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 c51bdf9f..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) 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 2d81adee..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 => 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/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 b971d438..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,11 +51,11 @@ 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)] + [Test, CancelAfter(TestTimeout)] public void P2PConnect() { var client1 = ManagerStack.Client(1); @@ -76,11 +71,33 @@ 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, 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, Timeout(TestTimeout)] + [Test, CancelAfter(TestTimeout)] public void ConnectionByIpV4Unsynced() { var server = ManagerStack.Server(1); @@ -94,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); @@ -108,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 => @@ -123,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; }; @@ -140,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); @@ -166,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); @@ -194,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); @@ -216,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); @@ -255,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) @@ -264,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; } @@ -272,7 +289,7 @@ public void ReconnectTest() server.PollEvents(); Thread.Sleep(15); } - Assert.AreEqual(2, connectCount); + Assert.That(connectCount, Is.EqualTo(2)); } [Test] @@ -289,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; }; @@ -303,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] @@ -321,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; }; @@ -335,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); @@ -357,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) { @@ -382,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); @@ -412,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(); @@ -437,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) { @@ -464,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) @@ -474,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); @@ -490,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; }; @@ -517,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; @@ -560,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); @@ -572,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); @@ -590,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); @@ -623,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); }; } @@ -639,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(); @@ -668,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); @@ -679,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; @@ -702,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); @@ -721,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 96504d06..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 b70c4360..1dd969c5 100644 --- a/LiteNetLib/BaseChannel.cs +++ b/LiteNetLib/BaseChannel.cs @@ -3,19 +3,38 @@ 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; + /// + /// 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; - } + /// + /// 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) { lock (OutgoingQueue) @@ -25,14 +44,20 @@ public void AddToQueue(NetPacket 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(); @@ -42,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 4a58e9ea..1b1249b1 100644 --- a/LiteNetLib/LiteNetLib.csproj +++ b/LiteNetLib/LiteNetLib.csproj @@ -3,16 +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.1.0 + 2.1.4 Lite reliable UDP library for Mono and .NET + true + true + 2.1.4 @@ -23,31 +25,27 @@ TRACE + + $(DefineConstants);SIMULATE_NETWORK + + true $(DefineConstants);LITENETLIB_UNSAFE udp reliable-udp network - https://github.com/RevenantX/LiteNetLib/releases/tag/v1.1.0 + https://github.com/RevenantX/LiteNetLib/releases/tag/2.1.4 git https://github.com/RevenantX/LiteNetLib https://github.com/RevenantX/LiteNetLib MIT True - 1.1.0 Ruslan Pyrch - Copyright 2023 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 98% rename from LiteNetLib/NetManager.PacketPool.cs rename to LiteNetLib/LiteNetManager.PacketPool.cs index 08312209..fd8520f6 100644 --- a/LiteNetLib/NetManager.PacketPool.cs +++ b/LiteNetLib/LiteNetManager.PacketPool.cs @@ -2,7 +2,7 @@ namespace LiteNetLib { - public partial class NetManager + public partial class LiteNetManager { private NetPacket _poolHead; private int _poolCount; diff --git a/LiteNetLib/NetManager.Socket.cs b/LiteNetLib/LiteNetManager.Socket.cs similarity index 72% rename from LiteNetLib/NetManager.Socket.cs rename to LiteNetLib/LiteNetManager.Socket.cs index aabeaa36..97d6870b 100644 --- a/LiteNetLib/NetManager.Socket.cs +++ b/LiteNetLib/LiteNetManager.Socket.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +#if UNITY_2018_3_OR_NEWER +#define UNITY_SOCKET_FIX +#endif +using System.Runtime.InteropServices; using System; using System.Collections.Generic; using System.Net; @@ -8,38 +11,35 @@ namespace LiteNetLib { - 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 _receiveThread; private IPEndPoint _bufferEndPointv4; private IPEndPoint _bufferEndPointv6; -#if UNITY_2018_3_OR_NEWER +#if UNITY_SOCKET_FIX private PausedSocketFix _pausedSocketFix; + private bool _useSocketFix; #endif -#if !LITENETLIB_UNSAFE - [ThreadStatic] private static byte[] _sendToBuffer; +#if NET8_0_OR_GREATER + private readonly SocketAddress _sockAddrCacheV4 = new SocketAddress(AddressFamily.InterNetwork); + private readonly SocketAddress _sockAddrCacheV6 = new SocketAddress(AddressFamily.InterNetworkV6); #endif - [ThreadStatic] private static byte[] _endPointBuffer; - - private readonly Dictionary _nativeAddrMap = new Dictionary(); 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; - /// - /// Maximum packets count that will be processed in Manual PollEvents - /// - public int MaxPacketsReceivePerUpdate = 0; - // special case in iOS (and possibly android that should be resolved in unity) internal bool NotConnected; + /// + /// Poll timeout in microseconds. Increasing can slightly increase performance in cost of slow NetManager.Stop(Socket.Close) + /// + public int ReceivePollingTime = 50000; //0.05 second + public short Ttl { get @@ -58,7 +58,7 @@ internal set } } - static NetManager() + static LiteNetManager() { #if DISABLE_IPV6 IPv6Support = false; @@ -70,23 +70,6 @@ static NetManager() #endif } - 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) @@ -102,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: @@ -117,12 +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)) { - ReceiveFrom(socket, ref bufferEndPoint); - packetsReceived++; - if (packetsReceived == MaxPacketsReceivePerUpdate) + if(ReceiveFrom(socket, ref bufferEndPoint) > 0) + count--; + else break; } } @@ -137,75 +121,54 @@ 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 bool NativeReceiveFrom(ref NetPacket packet, IntPtr s, byte[] addrBuffer, int addrSize) - { - //Reading data - packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, addrBuffer, ref addrSize); - if (packet.Size == 0) - return false; //socket closed - if (packet.Size == -1) - { - var errorCode = NativeSocket.GetSocketError(); - //Linux timeout EAGAIN - return errorCode == SocketError.WouldBlock || errorCode == SocketError.TimedOut || ProcessError(new SocketException((int)errorCode)) == false; - } - - var 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); - packet = PoolGetPacket(NetConstants.MaxPacketSize); - return true; - } - private void NativeReceiveLogic() { IntPtr socketHandle4 = _udpSocketv4.Handle; IntPtr socketHandle6 = _udpSocketv6?.Handle ?? IntPtr.Zero; byte[] addrBuffer4 = new byte[NativeSocket.IPv4AddrSize]; byte[] addrBuffer6 = new byte[NativeSocket.IPv6AddrSize]; - int addrSize4 = addrBuffer4.Length; - int addrSize6 = addrBuffer6.Length; + 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) + while (_isRunning) { - if (socketV6 == null) - { - if (NativeReceiveFrom(ref packet, socketHandle4, addrBuffer4, addrSize4) == false) - return; - continue; - } - bool messageReceived = false; - if (socketv4.Available != 0) - { - if (NativeReceiveFrom(ref packet, socketHandle4, addrBuffer4, addrSize4) == false) - return; - messageReceived = true; - } - if (socketV6.Available != 0) - { - if (NativeReceiveFrom(ref packet, socketHandle6, addrBuffer6, addrSize6) == false) - return; - messageReceived = true; - } - if (messageReceived) - continue; - selectReadList.Clear(); - selectReadList.Add(socketv4); - selectReadList.Add(socketV6); 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; + + selectReadList.Add(socketv4); + selectReadList.Add(socketV6); + Socket.Select(selectReadList, null, null, ReceivePollingTime); } catch (SocketException ex) @@ -226,16 +189,72 @@ private void NativeReceiveLogic() catch (Exception e) { //protects socket receive thread - NetDebug.WriteError("[NM] SocketReceiveThread error: " + e ); + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e); } } + + bool NativeReceiveFrom(IntPtr s, byte[] address) + { + int addrSize = address.Length; + packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, address, ref addrSize); + if (packet.Size == 0) + return true; //socket closed or empty packet + + if (packet.Size == -1) + { + //Linux timeout EAGAIN + return ProcessError(new SocketException((int)NativeSocket.GetSocketError())) == false; + } + + //NetDebug.WriteForce($"[R]Received data from {endPoint}, result: {packet.Size}"); + //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 ReceiveFrom(Socket s, ref EndPoint bufferEndPoint) + private int ReceiveFrom(Socket s, ref EndPoint bufferEndPoint) { 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; } private void ReceiveLogic() @@ -246,7 +265,7 @@ private void ReceiveLogic() var socketv4 = _udpSocketv4; var socketV6 = _udpSocketv6; - while (IsRunning) + while (_isRunning) { //Reading data try @@ -260,20 +279,22 @@ private void ReceiveLogic() else { bool messageReceived = false; - if (socketv4.Available != 0) + if (socketv4.Available != 0 || selectReadList.Contains(socketv4)) { ReceiveFrom(socketv4, ref bufferEndPoint4); messageReceived = true; } - if (socketV6.Available != 0) + if (socketV6.Available != 0 || selectReadList.Contains(socketV6)) { ReceiveFrom(socketV6, ref bufferEndPoint6); messageReceived = true; } + + selectReadList.Clear(); + if (messageReceived) continue; - selectReadList.Clear(); selectReadList.Add(socketv4); selectReadList.Add(socketV6); Socket.Select(selectReadList, null, null, ReceivePollingTime); @@ -298,18 +319,22 @@ private void ReceiveLogic() 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 && NotConnected == false) @@ -322,14 +347,14 @@ public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool m if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port))) return false; - LocalPort = ((IPEndPoint) _udpSocketv4.LocalEndPoint).Port; + LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port; -#if UNITY_2018_3_OR_NEWER - if (_pausedSocketFix == null) +#if UNITY_SOCKET_FIX + if (_useSocketFix && _pausedSocketFix == null) _pausedSocketFix = new PausedSocketFix(this, addressIPv4, addressIPv6, port, manualMode); #endif - IsRunning = true; + _isRunning = true; if (_manualMode) { _bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0); @@ -343,9 +368,7 @@ public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool m if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort))) { if (_manualMode) - { _bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0); - } } else { @@ -387,7 +410,7 @@ private bool BindSocket(Socket socket, IPEndPoint ep) { try { - socket.IOControl(SioUdpConnreset, new byte[] {0}, null); + socket.IOControl(SioUdpConnreset, new byte[] { 0 }, null); } catch { @@ -399,6 +422,7 @@ 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 { @@ -434,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, @@ -487,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; @@ -507,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) { @@ -518,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); - } - } -#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, dataWithOffset, length, peer.NativeAddress, peer.NativeAddress.Length); } - - 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); } @@ -603,10 +601,10 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd 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, @@ -631,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; @@ -651,15 +642,11 @@ 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) { @@ -704,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}"); @@ -719,14 +713,14 @@ public bool SendBroadcast(byte[] data, int start, int length, int port) private void CloseSocket() { - IsRunning = false; + _isRunning = false; + if (_receiveThread != null && _receiveThread != Thread.CurrentThread) + _receiveThread.Join(); + _receiveThread = null; _udpSocketv4?.Close(); _udpSocketv6?.Close(); _udpSocketv4 = null; _udpSocketv6 = null; - if (_receiveThread != null && _receiveThread != Thread.CurrentThread) - _receiveThread.Join(); - _receiveThread = 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 7d79ef8a..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) @@ -75,13 +119,16 @@ class NatPunchPacket 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); } 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/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 acd54f63..fc02a659 100644 --- a/LiteNetLib/NetManager.cs +++ b/LiteNetLib/NetManager.cs @@ -1,331 +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 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 NetEvent _pendingEventHead; - private NetEvent _pendingEventTail; - - 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 bool IPv6Enabled = true; - - /// - /// 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) @@ -342,232 +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.Next = null; - 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 (_eventLock) - { - if (_pendingEventTail == null) - _pendingEventHead = evt; - else - _pendingEventTail.Next = evt; - _pendingEventTail = 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 != null) - _peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, previousAddress); + if (previousAddress != null) + _netEventListener.OnPeerAddressChanged(netPeer, previousAddress); break; } //Recycle if not message @@ -623,574 +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: {netPeer.ConnectTime}, EP: {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: {netPeer.ConnectTime}, NewId: {connRequest.ConnectionTime}, EP: {remoteEndPoint}, Result: {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: {connRequest.ConnectionTime}, EP: {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(); - 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; - - 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; - } + _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 @@ -1198,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 @@ -1209,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 @@ -1222,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 { @@ -1237,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(); + } } /// @@ -1289,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 { @@ -1313,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 @@ -1322,183 +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; - 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 /// @@ -1506,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 @@ -1519,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 @@ -1541,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 @@ -1553,266 +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; - } + /// Manager is not running. Call + public new NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) => + (NetPeer)base.Connect(target, connectionData); /// - /// 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"); - -#if UNITY_2018_3_OR_NEWER - _pausedSocketFix.Deinitialize(); - _pausedSocketFix = null; -#endif - - //Send last disconnect - for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) - netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages); - - //Stop - CloseSocket(); - _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; - _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 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)); - } - - /// - /// 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 4b403084..d0ecdd6a 100644 --- a/LiteNetLib/NetPacket.cs +++ b/LiteNetLib/NetPacket.cs @@ -7,6 +7,7 @@ internal enum PacketProperty : byte { Unreliable, Channeled, + ReliableMerged, Ack, Ping, Pong, @@ -22,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() @@ -39,6 +41,7 @@ static NetPacket() { case PacketProperty.Channeled: case PacketProperty.Ack: + case PacketProperty.ReliableMerged: HeaderSizes[i] = NetConstants.ChanneledHeaderSize; break; case PacketProperty.Ping: @@ -63,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 66906789..3be8844e 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,833 +179,89 @@ 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 ((ReliableChannel)_channels[idx])?.PacketsInQueue ?? 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: - MTU: {mtu} - headerSize: {headerSize} - packetFullSize: {packetFullSize} - packetDataSize: {packetDataSize} - totalPackets: {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: {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)) - { - 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: {pos + writtenSize} >= resultPacketSize: {resultingPacket.RawData.Length} , totalSize: {incomingFragments.TotalSize}"); - return; - } - if (fragment.Size > fragment.RawData.Length) - { - _holdedFragments.Remove(packetFragId); - NetDebug.WriteError($"Fragment error size: {fragment.Size} > fragment.RawData.Length: {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 {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, _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: {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: {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: {_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 f7b2bd82..593c2f2a 100644 --- a/LiteNetLib/NetUtils.cs +++ b/LiteNetLib/NetUtils.cs @@ -25,11 +25,25 @@ public static class NetUtils { private static readonly NetworkSorter NetworkSorter = new NetworkSorter(); - public static IPEndPoint MakeEndPoint(string hostStr, int port) - { - return new IPEndPoint(ResolveAddress(hostStr), port); - } + /// + /// 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") @@ -37,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); @@ -48,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; @@ -158,7 +178,7 @@ public static string GetLocalIp(LocalAddrType addrType) // =========================================== internal static void PrintInterfaceInfos() { - NetDebug.WriteForce(NetLogLevel.Info, $"IPv6Support: { NetManager.IPv6Support}"); + NetDebug.WriteForce(NetLogLevel.Info, $"IPv6Support: { LiteNetManager.IPv6Support}"); try { foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) @@ -181,10 +201,8 @@ internal static void PrintInterfaceInfos() } } - 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 { diff --git a/LiteNetLib/PausedSocketFix.cs b/LiteNetLib/PausedSocketFix.cs index dc92122a..a8947348 100644 --- a/LiteNetLib/PausedSocketFix.cs +++ b/LiteNetLib/PausedSocketFix.cs @@ -1,32 +1,42 @@ #if UNITY_2018_3_OR_NEWER using System.Net; +using UnityEngine; namespace LiteNetLib { public class PausedSocketFix { - private readonly NetManager _netManager; + 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(NetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode) + public PausedSocketFix(LiteNetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode) { _netManager = netManager; _ipv4 = ipv4; _ipv6 = ipv6; _port = port; _manualMode = manualMode; - UnityEngine.Application.focusChanged += Application_focusChanged; + Application.focusChanged += Application_focusChanged; + Application.quitting += Deinitialize; _initialized = true; } public void Deinitialize() { if (_initialized) - UnityEngine.Application.focusChanged -= Application_focusChanged; + { + Application.focusChanged -= Application_focusChanged; + Application.quitting -= Deinitialize; + } + + if (_netManager.IsRunning) + { + _netManager.Stop(); + } _initialized = false; } 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 4a10d170..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; @@ -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: {pendingSeq}"); + //NetDebug.Write($"[PA]False ack: {pendingSeq}"); continue; } @@ -165,7 +273,7 @@ private void ProcessAck(NetPacket packet) } } - protected override bool SendNextPackets() + public override bool SendNextPackets() { if (_mustSendAcks) { @@ -189,7 +297,7 @@ protected override bool SendNextPackets() if (relate >= _windowSize) break; - var netPacket = OutgoingQueue.Dequeue(); + var netPacket = GetNextOutgoingPacket(); netPacket.Sequence = (ushort) _localSeqence; netPacket.ChannelId = _id; _pendingPackets[_localSeqence % _windowSize].Init(netPacket); @@ -295,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) @@ -305,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; } } @@ -329,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 bc47f86d..4bd1d778 100644 --- a/LiteNetLib/SequencedChannel.cs +++ b/LiteNetLib/SequencedChannel.cs @@ -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) { 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 03761310..2e230009 100644 --- a/LiteNetLib/Utils/NetDataReader.cs +++ b/LiteNetLib/Utils/NetDataReader.cs @@ -1,6 +1,8 @@ using System; using System.Net; +using System.Net.Sockets; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace LiteNetLib.Utils { @@ -9,59 +11,125 @@ public class NetDataReader protected byte[] _data; protected int _position; protected int _dataSize; - private int _offset; + protected int _offset; + private const int IPv4Size = 4; + private const int IPv6Size = 16; + private const int GuidSize = 16; + + /// + /// 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; } + + /// + /// Gets the starting offset of the user payload within the . + /// public int UserDataOffset { [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; } - public void SkipBytes(int count) + /// + /// 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) { - _position += 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; @@ -70,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; @@ -78,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; @@ -86,674 +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 + /// + /// Deserializes a that implements . + /// + /// A type implementing . + /// The deserialized output. public void Get(out T result) where T : struct, INetSerializable { - result = default(T); + result = default; result.Deserialize(this); } + /// + /// 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 { result = constructor(); result.Deserialize(this); } - public void Get(out IPEndPoint result) - { - result = GetNetEndPoint(); - } + /// + /// Deserializes an and assigns it to the parameter. + /// + /// The deserialized output. + public void Get(out IPEndPoint result) => result = GetIPEndPoint(); - public void Get(out byte result) + /// + /// 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() { - result = GetByte(); - } + bool isIPv4 = GetByte() == 0; - public void Get(out sbyte result) - { - result = (sbyte)GetByte(); - } + int size = isIPv4 ? IPv4Size : IPv6Size; + EnsureAvailable(size); - public void Get(out bool result) - { - result = GetBool(); + IPAddress address = new IPAddress(new ReadOnlySpan(_data, _position, size)); + _position += size; + return new IPEndPoint(address, GetUShort()); } - public void Get(out char result) - { - result = GetChar(); - } + /// Reads a and assigns it to . + public void Get(out byte result) => result = GetByte(); - public void Get(out ushort result) - { - result = GetUShort(); - } + /// Reads an and assigns it to . + public void Get(out sbyte result) => result = GetSByte(); - public void Get(out short result) - { - result = GetShort(); - } + /// Reads a and assigns it to . + public void Get(out bool result) => result = GetBool(); - public void Get(out ulong result) - { - result = GetULong(); - } + /// Reads a and assigns it to . + public void Get(out char result) => result = GetChar(); - public void Get(out long result) - { - result = GetLong(); - } + /// Reads a and assigns it to . + public void Get(out ushort result) => result = GetUShort(); - public void Get(out uint result) - { - result = GetUInt(); - } + /// Reads a and assigns it to . + public void Get(out short result) => result = GetShort(); - public void Get(out int result) - { - result = GetInt(); - } + /// Reads a and assigns it to . + public void Get(out ulong result) => result = GetULong(); - public void Get(out double result) - { - result = GetDouble(); - } + /// Reads a and assigns it to . + public void Get(out long result) => result = GetLong(); - public void Get(out float result) - { - result = GetFloat(); - } + /// Reads a and assigns it to . + public void Get(out uint result) => result = GetUInt(); - public void Get(out string result) - { - result = GetString(); - } + /// Reads an and assigns it to . + public void Get(out int result) => result = GetInt(); - public void Get(out string result, int maxLength) - { - result = GetString(maxLength); - } + /// Reads a and assigns it to . + public void Get(out double result) => result = GetDouble(); - public IPEndPoint GetNetEndPoint() - { - string host = GetString(1000); - int port = GetInt(); - return NetUtils.MakeEndPoint(host, port); - } + /// Reads a and assigns it to . + public void Get(out float result) => result = GetFloat(); - public byte GetByte() - { - byte res = _data[_position]; - _position++; - return res; - } + /// 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 sbyte GetSByte() + /// + /// 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 { - return (sbyte)GetByte(); + 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; } + /// + /// 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) { - ushort length = BitConverter.ToUInt16(_data, _position); - _position += 2; + ushort length = GetUShort(); + + int byteLength = checked(length * size); + EnsureAvailable(byteLength); + T[] result = new T[length]; - length *= size; - Buffer.BlockCopy(_data, _position, result, 0, length); - _position += length; + if (byteLength > 0) + Buffer.BlockCopy(_data, _position, result, 0, byteLength); + + _position += byteLength; return result; } - public bool[] GetBoolArray() + /// + /// 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() { - return GetArray(1); + 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 ushort[] GetUShortArray() + /// + /// 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 { - return GetArray(2); + ushort length = GetUShort(); + T[] result = new T[length]; + for (int i = 0; i < length; i++) + Get(out result[i], constructor); + return result; } - public short[] GetShortArray() - { - return GetArray(2); - } + /// Reads an array of values. + public bool[] GetBoolArray() => GetUnmanagedArray(); - public int[] GetIntArray() - { - return GetArray(4); - } + /// Reads an array of values. + public ushort[] GetUShortArray() => GetUnmanagedArray(); - public uint[] GetUIntArray() - { - return GetArray(4); - } + /// Reads an array of values. + public short[] GetShortArray() => GetUnmanagedArray(); - public float[] GetFloatArray() - { - return GetArray(4); - } + /// Reads an array of values. + public int[] GetIntArray() => GetUnmanagedArray(); - public double[] GetDoubleArray() - { - return GetArray(8); - } + /// Reads an array of values. + public uint[] GetUIntArray() => GetUnmanagedArray(); - public long[] GetLongArray() - { - return GetArray(8); - } + /// Reads an array of values. + public float[] GetFloatArray() => GetUnmanagedArray(); - public ulong[] GetULongArray() - { - return GetArray(8); - } + /// 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() { ushort length = GetUShort(); - string[] arr = new string[length]; + EnsureAvailable(checked(length * sizeof(ushort))); // 2 bytes (ushort) for string length + + string[] result = new string[length]; for (int i = 0; i < length; i++) { - arr[i] = GetString(); + result[i] = GetString(); } - return arr; + + return result; } /// - /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. - /// Strings that exceed this parameter are returned as empty + /// 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) { ushort length = GetUShort(); - string[] arr = new string[length]; + EnsureAvailable(checked(length * sizeof(ushort))); // 2 bytes (ushort) for string length + + string[] result = new string[length]; for (int i = 0; i < length; i++) { - arr[i] = GetString(maxStringLength); + result[i] = GetString(maxStringLength); } - return arr; - } - public bool GetBool() - { - return GetByte() == 1; + return result; } - public char GetChar() + /// Reads the next from the buffer. + public byte GetByte() { - return (char)GetUShort(); + EnsureAvailable(1); + return _data[_position++]; } - public ushort GetUShort() - { - ushort result = BitConverter.ToUInt16(_data, _position); - _position += 2; - return result; - } + /// Reads the next from the buffer. + public sbyte GetSByte() => (sbyte)GetByte(); - public short GetShort() - { - short result = BitConverter.ToInt16(_data, _position); - _position += 2; - return result; - } + /// Reads a value from the current position. + /// if the byte is 1; otherwise, . + public bool GetBool() => GetByte() == 1; - public long GetLong() - { - long result = BitConverter.ToInt64(_data, _position); - _position += 8; - return result; - } + /// Reads a value as a 2-byte . + public char GetChar() => GetUnmanaged(); - public ulong GetULong() - { - ulong result = BitConverter.ToUInt64(_data, _position); - _position += 8; - return result; - } + /// Reads a value using unmanaged memory access. + public ushort GetUShort() => GetUnmanaged(); - public int GetInt() - { - int result = BitConverter.ToInt32(_data, _position); - _position += 4; - return result; - } + /// Reads a value using unmanaged memory access. + public short GetShort() => GetUnmanaged(); - public uint GetUInt() - { - uint result = BitConverter.ToUInt32(_data, _position); - _position += 4; - return result; - } + /// Reads a value using unmanaged memory access. + public long GetLong() => GetUnmanaged(); - public float GetFloat() - { - float result = BitConverter.ToSingle(_data, _position); - _position += 4; - return result; - } + /// Reads a value using unmanaged memory access. + public ulong GetULong() => GetUnmanaged(); - public double GetDouble() - { - double result = BitConverter.ToDouble(_data, _position); - _position += 8; - return result; - } + /// 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 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 && NetDataWriter.uTF8Encoding.Value.GetCharCount(data.Array, data.Offset, data.Count) > maxLength) ? - string.Empty : - NetDataWriter.uTF8Encoding.Value.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 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 NetDataWriter.uTF8Encoding.Value.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 : struct, INetSerializable + /// + /// 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 obj = default(T); - obj.Deserialize(this); - return obj; + var result = new ReadOnlySpan(_data, _position, AvailableBytes); + _position = _dataSize; + return result; } - public T Get(Func constructor) where T : class, INetSerializable + /// + /// 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 = constructor(); - 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() - { - return GetArray(1); + /// + /// 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() - { - return GetArray(1); + /// + /// 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] == 1; - } + /// 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(); /// - /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// 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 string.Empty; - } int actualSize = size - 1; - if (actualSize >= NetDataWriter.StringBufferMaxLength) - { - return null; - } - - return (maxLength > 0 && NetDataWriter.uTF8Encoding.Value.GetCharCount(_data, _position + 2, actualSize) > maxLength) ? - string.Empty : - NetDataWriter.uTF8Encoding.Value.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 string.Empty; - } int actualSize = size - 1; - if (actualSize >= NetDataWriter.StringBufferMaxLength) - { - return null; - } - - return NetDataWriter.uTF8Encoding.Value.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) { - if (!TryGetUShort(out ushort 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 >= 2) + /// + /// 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) { - ushort length = PeekUShort(); - if (length >= 0 && AvailableBytes >= 2 + length) - { - 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 baa8351a..24f09b8d 100644 --- a/LiteNetLib/Utils/NetDataWriter.cs +++ b/LiteNetLib/Utils/NetDataWriter.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Net; +using System.Net.Sockets; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; -using System.Threading; namespace LiteNetLib.Utils { @@ -13,33 +14,58 @@ public class NetDataWriter private const int InitialSize = 64; private readonly bool _autoResize; + private const int IPv4Size = 4; + private const int IPv6Size = 16; + private const int GuidSize = 16; + + /// + /// Gets the total capacity of the internal buffer. + /// + /// The length of the underlying array. public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _data.Length; } + + /// + /// 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; } + + /// + /// Gets the current number of s written to the buffer. + /// + /// The current . public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _position; } - public static readonly ThreadLocal uTF8Encoding = new ThreadLocal(() => new UTF8Encoding(false, true)); - public const int StringBufferMaxLength = 65535; - private readonly byte[] _stringBuffer = new byte[StringBufferMaxLength]; + /// + /// 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); - public NetDataWriter() : this(true, InitialSize) - { - } + internal static readonly UTF8Encoding uTF8Encoding = new UTF8Encoding(false, true); - public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) - { - } + public NetDataWriter() : this(true, InitialSize) { } + + public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) { } public NetDataWriter(bool autoResize, int initialSize) { @@ -71,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(); @@ -83,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) { @@ -92,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) { @@ -101,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]; @@ -131,143 +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); - public void Put(byte value) + /// + /// Serializes a value. + /// + /// The value to write. + public void Put(byte value) => PutUnmanaged(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; } + /// + /// 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 + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); - Buffer.BlockCopy(data, offset, _data, _position + 2, length); - _position += 2 + length; - } + _position += 2; - public void PutSBytesWithLength(sbyte[] data) - { - PutArray(data, 1); + if (length > 0) + { + ReadOnlySpan source = data.AsSpan(offset, length); + ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); + + sourceBytes.CopyTo(_data.AsSpan(_position)); + _position += 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 + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); - Buffer.BlockCopy(data, offset, _data, _position + 2, length); - _position += 2 + length; - } + _position += 2; - public void PutBytesWithLength(byte[] data) - { - PutArray(data, 1); + 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); + + /// + /// 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; + ushort length = arr == null ? (ushort)0 : (ushort)arr.Length; sz *= length; if (_autoResize) ResizeIfNeed(_position + sz + 2); @@ -277,51 +397,80 @@ public 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; @@ -330,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; @@ -338,21 +492,100 @@ public void PutArray(string[] value, int strMaxLength) Put(value[i], strMaxLength); } + /// + /// 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() + { + ushort strArrayLength = (ushort)(value?.Length ?? 0); + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + value[i].Serialize(this); + } + + /// + /// 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(endPoint.Address.ToString()); - Put(endPoint.Port); + 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); } - public void Put(string value) + /// + /// Serializes a using a 4-byte length header. + /// + /// The to write. + /// + /// Recommended for strings that may exceed the 65535 byte limit of standard length headers.
+ /// Uses . + ///
+ public void PutLargeString(string value) { - Put(value, 0); + if (string.IsNullOrEmpty(value)) + { + Put(0); + return; + } + + int size = uTF8Encoding.GetByteCount(value); + Put(size); + + if (_autoResize) + ResizeIfNeed(_position + size); + + uTF8Encoding.GetBytes(value, 0, value.Length, _data, _position); + _position += size; } /// - /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// Serializes a string using a 2-byte length header. /// - public void Put(string value, int maxLength) + /// 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)) { @@ -360,22 +593,89 @@ public void Put(string value, int maxLength) return; } - int length = maxLength > 0 && value.Length > maxLength ? maxLength : value.Length; - int size = uTF8Encoding.Value.GetBytes(value, 0, length, _stringBuffer, 0); + ReadOnlySpan source = value.AsSpan(); + if (maxLength > 0 && source.Length > maxLength) + { + source = source.Slice(0, maxLength); + } - if (size == 0 || size >= StringBufferMaxLength) + 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 f3d1493e..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 { @@ -117,7 +118,11 @@ public void ReadPacket(NetDataReader reader) ReadPacket(reader, null); } - 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); @@ -146,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) => @@ -163,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) => @@ -180,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(); @@ -197,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(); @@ -208,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 @@ -220,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 @@ -232,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() { @@ -243,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 index b73e1b90..057798b9 100644 --- a/LiteNetLib/Utils/Preserve.cs +++ b/LiteNetLib/Utils/Preserve.cs @@ -3,7 +3,7 @@ namespace LiteNetLib.Utils { /// - /// PreserveAttribute prevents byte code stripping from removing a class, method, field, or property. + /// 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/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 6e6c599e..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 42d376c0..00000000 --- a/LiteNetLibSampleUnity/Assets/LiteNetLib.xml +++ /dev/null @@ -1,1497 +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 "maxStringLength" only limits the number of characters in a string, not its size in bytes. - Strings that exceed this parameter are returned as empty - - - - - Note that "maxLength" only limits the number of characters in a string, not its size in bytes. - - "string.Empty" if value > "maxLength" - - - - Note that "maxLength" only limits the number of characters in a string, not its size in bytes. - - - - - 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 48d2e622..85caa020 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ -# LiteNetLib +# 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) **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) @@ -47,32 +49,39 @@ Lite reliable UDP library for .NET Standard 2.0 (Mono, .NET Core, .NET Framework * NTP time requests * Packet loss and latency simulation * IPv6 support (using separate socket for performance) -* Connection statisitcs +* 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) +* [!["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, channel) => +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(); @@ -88,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 => @@ -102,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) @@ -115,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/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 2ac6eed4..1c60018b 100644 --- a/docs/api/LiteNetLib.ConnectionRequest.html +++ b/docs/api/LiteNetLib.ConnectionRequest.html @@ -1,15 +1,14 @@ - + - Class ConnectionRequest - + Class ConnectionRequest - + + @@ -71,74 +70,57 @@ -

Class ConnectionRequest -

+

Class ConnectionRequest

Inheritance
- -
ConnectionRequest
+
object
+ +
ConnectionRequest
+
+
Namespace: LiteNetLib
Assembly: LiteNetLib.dll
Syntax
-
public class ConnectionRequest
-
-

Fields -

- - -

RemoteEndPoint

-
-
-
Declaration
-
-
public readonly IPEndPoint RemoteEndPoint
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
IPEndPoint
-

Properties -

- - - -

Data

-
-
-
Declaration
-
-
public NetDataReader Data { get; }
+
public class ConnectionRequest : LiteConnectionRequest
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
NetDataReader

Methods

@@ -172,7 +154,8 @@
Returns

AcceptIfKey(string)

-
+

Accepts the connection if the first string in the Data matches the provided key.

+
Declaration
@@ -191,7 +174,8 @@
Parameters
string key - +

The required string key to match.

+ @@ -206,254 +190,15 @@
Returns
NetPeer - - - - - - - -

Reject()

-
-
-
Declaration
-
-
public void Reject()
-
- - - -

Reject(NetDataWriter)

-
-
-
Declaration
-
-
public void Reject(NetDataWriter rejectData)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterrejectData
- - - -

Reject(byte[], int, int, bool)

-
-
-
Declaration
-
-
public void Reject(byte[] rejectData, int start, int length, bool force)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]rejectData
intstart
intlength
boolforce
- - - -

Reject(byte[], int, int)

-
-
-
Declaration
-
-
public void Reject(byte[] rejectData, int start, int length)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]rejectData
intstart
intlength
- - - -

Reject(byte[])

-
-
-
Declaration
-
-
public void Reject(byte[] rejectData)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
byte[]rejectData
- - - -

RejectForce()

-
-
-
Declaration
-
-
public void RejectForce()
-
- - - -

RejectForce(NetDataWriter)

-
-
-
Declaration
-
-
public void RejectForce(NetDataWriter rejectData)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterrejectData
- - - -

RejectForce(byte[], int, int)

-
-
-
Declaration
-
-
public void RejectForce(byte[] rejectData, int start, int length)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]rejectData
intstart
intlength
- - - -

RejectForce(byte[])

-
-
-
Declaration
-
-
public void RejectForce(byte[] rejectData)
-
-
Parameters
- - - - - - - - - - - - - +
TypeNameDescription
byte[]rejectData

A new LiteNetPeer if the key matches and connection is accepted; otherwise, null.

+
+
Remarks
+

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.

+
@@ -465,7 +210,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.ConnectionState.html b/docs/api/LiteNetLib.ConnectionState.html index 95321853..c6497d19 100644 --- a/docs/api/LiteNetLib.ConnectionState.html +++ b/docs/api/LiteNetLib.ConnectionState.html @@ -1,16 +1,15 @@ - + - Enum ConnectionState - + Enum ConnectionState - + + @@ -72,8 +71,7 @@ -

Enum ConnectionState -

+

Enum ConnectionState

Peer connection state

@@ -93,7 +91,7 @@

Fields Description - + Any @@ -119,7 +117,7 @@

Fields - + @@ -130,7 +128,7 @@

Fields diff --git a/docs/api/LiteNetLib.DeliveryMethod.html b/docs/api/LiteNetLib.DeliveryMethod.html index 96b966e1..1d830771 100644 --- a/docs/api/LiteNetLib.DeliveryMethod.html +++ b/docs/api/LiteNetLib.DeliveryMethod.html @@ -1,16 +1,15 @@ - + - Enum DeliveryMethod - + Enum DeliveryMethod - + + @@ -72,8 +71,7 @@ -

Enum DeliveryMethod -

+

Enum DeliveryMethod

Sending method type

@@ -92,7 +90,7 @@

Fields Description - + ReliableOrdered

Reliable and ordered. Packets won't be dropped, won't be duplicated, will arrive in order.

@@ -120,7 +118,7 @@

Fields - + @@ -131,7 +129,7 @@

Fields diff --git a/docs/api/LiteNetLib.DisconnectInfo.html b/docs/api/LiteNetLib.DisconnectInfo.html index 045f9553..74879117 100644 --- a/docs/api/LiteNetLib.DisconnectInfo.html +++ b/docs/api/LiteNetLib.DisconnectInfo.html @@ -1,16 +1,15 @@ - + - Struct DisconnectInfo - + Struct DisconnectInfo - + + @@ -71,8 +70,7 @@ -

Struct DisconnectInfo -

+

Struct DisconnectInfo

Additional information about disconnection

@@ -170,7 +168,7 @@
Field Value
diff --git a/docs/api/LiteNetLib.DisconnectReason.html b/docs/api/LiteNetLib.DisconnectReason.html index 44cb6e20..0a39bf21 100644 --- a/docs/api/LiteNetLib.DisconnectReason.html +++ b/docs/api/LiteNetLib.DisconnectReason.html @@ -1,16 +1,15 @@ - + - Enum DisconnectReason - + Enum DisconnectReason - + + @@ -72,8 +71,7 @@ -

Enum DisconnectReason -

+

Enum DisconnectReason

Disconnect reason that you receive in OnPeerDisconnected event

@@ -92,18 +90,21 @@

Fields Description - + ConnectionFailed - +

Connection to host failed

+ ConnectionRejected - +

Connection rejected by remote host

+ DisconnectPeerCalled - +

Disconnect called locally

+ HostUnreachable @@ -131,18 +132,20 @@

Fields RemoteConnectionClose - +

Remote host disconnected peer

+ Timeout - +

Timeout

+ UnknownHost - + @@ -153,7 +156,7 @@

Fields 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 bda25ccb..64a64655 100644 --- a/docs/api/LiteNetLib.EventBasedNatPunchListener.html +++ b/docs/api/LiteNetLib.EventBasedNatPunchListener.html @@ -1,16 +1,15 @@ - + - Class EventBasedNatPunchListener - + Class EventBasedNatPunchListener - + + @@ -71,13 +70,13 @@ -

Class EventBasedNatPunchListener -

-
+

Class EventBasedNatPunchListener

+

An implementation of INatPunchListener that maps callbacks to events.

+
Inheritance
- +
object
EventBasedNatPunchListener
@@ -95,7 +94,8 @@

Events

NatIntroductionRequest

-
+

Event triggered when a NAT introduction request is received.

+
Declaration
@@ -111,7 +111,7 @@
Event Type
- EventBasedNatPunchListener.OnNatIntroductionRequest + EventBasedNatPunchListener.OnNatIntroductionRequest @@ -119,7 +119,8 @@
Event Type

NatIntroductionSuccess

-
+

Event triggered when NAT punchthrough is successfully completed.

+
Declaration
@@ -135,7 +136,7 @@
Event Type
- EventBasedNatPunchListener.OnNatIntroductionSuccess + EventBasedNatPunchListener.OnNatIntroductionSuccess @@ -155,7 +156,7 @@

Implements

diff --git a/docs/api/LiteNetLib.EventBasedNetListener.html b/docs/api/LiteNetLib.EventBasedNetListener.html index a9ce1833..d319a771 100644 --- a/docs/api/LiteNetLib.EventBasedNetListener.html +++ b/docs/api/LiteNetLib.EventBasedNetListener.html @@ -1,16 +1,15 @@ - + - Class EventBasedNetListener - + Class EventBasedNetListener - + + @@ -71,27 +70,24 @@ -

Class EventBasedNetListener -

-
+

Class EventBasedNetListener

+

Simple event based listener for simple setups and benchmarks

+
Inheritance
- +
object
EventBasedNetListener
Namespace: LiteNetLib
Assembly: LiteNetLib.dll
Syntax
-
public class EventBasedNetListener : INetEventListener, IDeliveryEventListener, INtpEventListener, IPeerAddressChangedListener
+
public class EventBasedNetListener : INetEventListener

Methods

@@ -99,7 +95,8 @@

Methods

ClearConnectionRequestEvent()

-
+

Clears all subscribers from ConnectionRequestEvent.

+
Declaration
@@ -109,7 +106,8 @@
Declaration

ClearDeliveryEvent()

-
+

Clears all subscribers from DeliveryEvent.

+
Declaration
@@ -119,7 +117,8 @@
Declaration

ClearNetworkErrorEvent()

-
+

Clears all subscribers from NetworkErrorEvent.

+
Declaration
@@ -129,7 +128,8 @@
Declaration

ClearNetworkLatencyUpdateEvent()

-
+

Clears all subscribers from NetworkLatencyUpdateEvent.

+
Declaration
@@ -139,7 +139,8 @@
Declaration

ClearNetworkReceiveEvent()

-
+

Clears all subscribers from NetworkReceiveEvent.

+
Declaration
@@ -149,7 +150,8 @@
Declaration

ClearNetworkReceiveUnconnectedEvent()

-
+

Clears all subscribers from NetworkReceiveUnconnectedEvent.

+
Declaration
@@ -159,7 +161,8 @@
Declaration

ClearNtpResponseEvent()

-
+

Clears all subscribers from NtpResponseEvent.

+
Declaration
@@ -169,7 +172,8 @@
Declaration

ClearPeerAddressChangedEvent()

-
+

Clears all subscribers from PeerAddressChangedEvent.

+
Declaration
@@ -179,7 +183,8 @@
Declaration

ClearPeerConnectedEvent()

-
+

Clears all subscribers from PeerConnectedEvent.

+
Declaration
@@ -189,7 +194,8 @@
Declaration

ClearPeerDisconnectedEvent()

-
+

Clears all subscribers from PeerDisconnectedEvent.

+
Declaration
@@ -200,7 +206,8 @@

Events

ConnectionRequestEvent

-
+

Occurs when a new connection request is received.

+
Declaration
@@ -216,7 +223,7 @@
Event Type
- EventBasedNetListener.OnConnectionRequest + EventBasedNetListener.OnConnectionRequest @@ -224,7 +231,8 @@
Event Type

DeliveryEvent

-
+

Occurs when a reliable packet is successfully delivered or acknowledged.

+
Declaration
@@ -240,7 +248,7 @@
Event Type
- EventBasedNetListener.OnDeliveryEvent + EventBasedNetListener.OnDeliveryEvent @@ -248,7 +256,8 @@
Event Type

NetworkErrorEvent

-
+

Occurs when a network error is detected in the underlying socket.

+
Declaration
@@ -264,7 +273,7 @@
Event Type
- EventBasedNetListener.OnNetworkError + EventBasedNetListener.OnNetworkError @@ -272,7 +281,8 @@
Event Type

NetworkLatencyUpdateEvent

-
+

Occurs when the round-trip time (RTT) to a peer is updated.

+
Declaration
@@ -288,7 +298,7 @@
Event Type
- EventBasedNetListener.OnNetworkLatencyUpdate + EventBasedNetListener.OnNetworkLatencyUpdate @@ -296,7 +306,8 @@
Event Type

NetworkReceiveEvent

-
+

Occurs when data is received from a connected peer.

+
Declaration
@@ -312,7 +323,7 @@
Event Type
- EventBasedNetListener.OnNetworkReceive + EventBasedNetListener.OnNetworkReceive @@ -320,7 +331,8 @@
Event Type

NetworkReceiveUnconnectedEvent

-
+

Occurs when a message is received from an unconnected endpoint.

+
Declaration
@@ -336,7 +348,7 @@
Event Type
- EventBasedNetListener.OnNetworkReceiveUnconnected + EventBasedNetListener.OnNetworkReceiveUnconnected @@ -344,7 +356,8 @@
Event Type

NtpResponseEvent

-
+

Occurs when an NTP response is received.

+
Declaration
@@ -360,7 +373,7 @@
Event Type
- EventBasedNetListener.OnNtpResponseEvent + EventBasedNetListener.OnNtpResponseEvent @@ -368,7 +381,8 @@
Event Type

PeerAddressChangedEvent

-
+

Occurs when a peer's remote address changes.

+
Declaration
@@ -384,7 +398,7 @@
Event Type
- EventBasedNetListener.OnPeerAddressChangedEvent + EventBasedNetListener.OnPeerAddressChangedEvent @@ -392,7 +406,8 @@
Event Type

PeerConnectedEvent

-
+

Occurs when a new peer has successfully connected.

+
Declaration
@@ -408,7 +423,7 @@
Event Type
- EventBasedNetListener.OnPeerConnected + EventBasedNetListener.OnPeerConnected @@ -416,7 +431,8 @@
Event Type

PeerDisconnectedEvent

-
+

Occurs when a peer disconnects or the connection is lost.

+
Declaration
@@ -461,7 +468,7 @@

Implements

diff --git a/docs/api/LiteNetLib.IDeliveryEventListener.html b/docs/api/LiteNetLib.IDeliveryEventListener.html index 5675990e..b64791e0 100644 --- a/docs/api/LiteNetLib.IDeliveryEventListener.html +++ b/docs/api/LiteNetLib.IDeliveryEventListener.html @@ -1,15 +1,14 @@ - + - Interface IDeliveryEventListener - + Interface IDeliveryEventListener - + + @@ -71,8 +70,7 @@ -

Interface IDeliveryEventListener -

+

Interface IDeliveryEventListener

Namespace: LiteNetLib
@@ -110,7 +108,7 @@
Parameters
- object + object userData @@ -127,7 +125,7 @@
Parameters
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 01ce3662..579fcb36 100644 --- a/docs/api/LiteNetLib.INatPunchListener.html +++ b/docs/api/LiteNetLib.INatPunchListener.html @@ -1,16 +1,15 @@ - + - Interface INatPunchListener - + Interface INatPunchListener - + + @@ -71,9 +70,9 @@ -

Interface INatPunchListener -

-
+

Interface INatPunchListener

+

Interface for handling events related to NAT punchthrough and introduction.

+
Namespace: LiteNetLib
Assembly: LiteNetLib.dll
@@ -87,7 +86,8 @@

Methods

OnNatIntroductionRequest(IPEndPoint, IPEndPoint, string)

-
+

Called when a NAT introduction request is received from the mediator server.

+
Declaration
@@ -106,17 +106,20 @@
Parameters
IPEndPoint localEndPoint - +

The local endpoint of the client requesting connection.

+ IPEndPoint remoteEndPoint - +

The remote endpoint of the client requesting connection.

+ string token - +

Custom data token associated with the request.

+ @@ -124,7 +127,8 @@
Parameters

OnNatIntroductionSuccess(IPEndPoint, NatAddressType, string)

-
+

Called when NAT punchthrough is successful and a direct connection can be established.

+
Declaration
@@ -143,17 +147,20 @@
Parameters
IPEndPoint targetEndPoint - +

The resolved endpoint of the remote peer.

+ NatAddressType type - +

The type of address (Internal or External) that succeeded.

+ string token - +

Custom data token associated with the request.

+ @@ -168,7 +175,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.INetEventListener.html b/docs/api/LiteNetLib.INetEventListener.html index dd5989ec..69aea427 100644 --- a/docs/api/LiteNetLib.INetEventListener.html +++ b/docs/api/LiteNetLib.INetEventListener.html @@ -1,16 +1,15 @@ - + - Interface INetEventListener - + Interface INetEventListener - + + @@ -71,9 +70,9 @@ -

Interface INetEventListener -

-
+

Interface INetEventListener

+

Interface for implementing own INetEventListener. This is a bit faster than use EventBasedListener

+
Namespace: LiteNetLib
Assembly: LiteNetLib.dll
@@ -114,6 +113,39 @@
Parameters
+ +

OnMessageDelivered(NetPeer, object)

+

On reliable message delivered

+
+
+
Declaration
+
+
void OnMessageDelivered(NetPeer peer, object userData)
+
+
Parameters
+ + + + + + + + + + + + + + + + + + + + +
TypeNameDescription
NetPeerpeer
objectuserData
+ +

OnNetworkError(IPEndPoint, SocketError)

Network error (on send or receive)

@@ -272,6 +304,69 @@
Parameters
+ +

OnNtpResponse(NtpPacket)

+

Ntp response

+
+
+
Declaration
+
+
void OnNtpResponse(NtpPacket packet)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
NtpPacketpacket
+ + + +

OnPeerAddressChanged(NetPeer, IPEndPoint)

+

Called when peer address changed (when AllowPeerAddressChange is enabled)

+
+
+
Declaration
+
+
void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress)
+
+
Parameters
+ + + + + + + + + + + + + + + + + + + + +
TypeNameDescription
NetPeerpeer

Peer that changed address (with new address)

+
IPEndPointpreviousAddress

previous IP

+
+ +

OnPeerConnected(NetPeer)

New remote peer connected to host, or client connected to remote host

@@ -345,7 +440,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.INetLogger.html b/docs/api/LiteNetLib.INetLogger.html index 725cc3d8..51136839 100644 --- a/docs/api/LiteNetLib.INetLogger.html +++ b/docs/api/LiteNetLib.INetLogger.html @@ -1,16 +1,15 @@ - + - Interface INetLogger - + Interface INetLogger - + + @@ -71,8 +70,7 @@ -

Interface INetLogger -

+

Interface INetLogger

Interface to implement for your own logger

@@ -115,7 +113,7 @@
Parameters
- object[] + object[] args @@ -132,7 +130,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.INtpEventListener.html b/docs/api/LiteNetLib.INtpEventListener.html index 5df04d5d..8fcaeeec 100644 --- a/docs/api/LiteNetLib.INtpEventListener.html +++ b/docs/api/LiteNetLib.INtpEventListener.html @@ -1,15 +1,14 @@ - + - Interface INtpEventListener - + Interface INtpEventListener - + + @@ -71,8 +70,7 @@ -

Interface INtpEventListener -

+

Interface INtpEventListener

Namespace: LiteNetLib
@@ -122,7 +120,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.IPeerAddressChangedListener.html b/docs/api/LiteNetLib.IPeerAddressChangedListener.html index 44c8a52d..9deead5b 100644 --- a/docs/api/LiteNetLib.IPeerAddressChangedListener.html +++ b/docs/api/LiteNetLib.IPeerAddressChangedListener.html @@ -1,15 +1,14 @@ - + - Interface IPeerAddressChangedListener - + Interface IPeerAddressChangedListener - + + @@ -71,8 +70,7 @@ -

Interface IPeerAddressChangedListener -

+

Interface IPeerAddressChangedListener

Namespace: LiteNetLib
@@ -129,7 +127,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.IPv6Mode.html b/docs/api/LiteNetLib.IPv6Mode.html index dd9ea3be..20d8ea81 100644 --- a/docs/api/LiteNetLib.IPv6Mode.html +++ b/docs/api/LiteNetLib.IPv6Mode.html @@ -1,15 +1,14 @@ - + - Enum IPv6Mode - + Enum IPv6Mode - + + @@ -72,8 +71,7 @@ -

Enum IPv6Mode -

+

Enum IPv6Mode

Namespace: LiteNetLib
@@ -91,7 +89,7 @@

Fields Description - + Disabled @@ -105,7 +103,7 @@

Fields - +

@@ -116,7 +114,7 @@

Fields

diff --git a/docs/api/LiteNetLib.InvalidPacketException.html b/docs/api/LiteNetLib.InvalidPacketException.html index 998fe148..5a7d107c 100644 --- a/docs/api/LiteNetLib.InvalidPacketException.html +++ b/docs/api/LiteNetLib.InvalidPacketException.html @@ -1,15 +1,14 @@ - + - Class InvalidPacketException - + Class InvalidPacketException - + + @@ -71,13 +70,12 @@ -

Class InvalidPacketException -

+

Class InvalidPacketException

Inheritance
- +
object
@@ -183,7 +181,7 @@

Implements

diff --git a/docs/api/LiteNetLib.Layers.Crc32cLayer.html b/docs/api/LiteNetLib.Layers.Crc32cLayer.html index 6a0f6853..11ffd257 100644 --- a/docs/api/LiteNetLib.Layers.Crc32cLayer.html +++ b/docs/api/LiteNetLib.Layers.Crc32cLayer.html @@ -1,15 +1,14 @@ - + - Class Crc32cLayer - + Class Crc32cLayer - + + @@ -71,13 +70,12 @@ -

Class Crc32cLayer -

+

Class Crc32cLayer

Inheritance
- +
object
Crc32cLayer
@@ -110,12 +108,12 @@

Methods -

ProcessInboundPacket(ref IPEndPoint, ref byte[], ref int, ref int)

+

ProcessInboundPacket(ref IPEndPoint, ref byte[], ref int)

Declaration
-
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)
Parameters
@@ -137,11 +135,6 @@
Parameters
- - - - - @@ -150,7 +143,7 @@
Parameters
data
intoffset
int length
Overrides
- + @@ -206,7 +199,7 @@
Overrides
diff --git a/docs/api/LiteNetLib.Layers.PacketLayerBase.html b/docs/api/LiteNetLib.Layers.PacketLayerBase.html index abbeb9ad..1a8c519a 100644 --- a/docs/api/LiteNetLib.Layers.PacketLayerBase.html +++ b/docs/api/LiteNetLib.Layers.PacketLayerBase.html @@ -1,15 +1,14 @@ - + - Class PacketLayerBase - + Class PacketLayerBase - + + @@ -71,13 +70,12 @@ -

Class PacketLayerBase -

+

Class PacketLayerBase

Inheritance
- +
object
PacketLayerBase
@@ -148,12 +146,12 @@

Methods -

ProcessInboundPacket(ref IPEndPoint, ref byte[], ref int, ref int)

+

ProcessInboundPacket(ref IPEndPoint, ref byte[], ref int)

Declaration
-
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)
Parameters
@@ -175,11 +173,6 @@
Parameters
- - - - - @@ -240,7 +233,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.Layers.XorEncryptLayer.html b/docs/api/LiteNetLib.Layers.XorEncryptLayer.html index 08e13b04..6ef948af 100644 --- a/docs/api/LiteNetLib.Layers.XorEncryptLayer.html +++ b/docs/api/LiteNetLib.Layers.XorEncryptLayer.html @@ -1,15 +1,14 @@ - + - Class XorEncryptLayer - + Class XorEncryptLayer - + + @@ -71,13 +70,12 @@ -

Class XorEncryptLayer -

+

Class XorEncryptLayer

Inheritance
- +
object
XorEncryptLayer
@@ -164,12 +162,12 @@

Methods -

ProcessInboundPacket(ref IPEndPoint, ref byte[], ref int, ref int)

+

ProcessInboundPacket(ref IPEndPoint, ref byte[], ref int)

Declaration
-
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)
Parameters
data
intoffset
int length
@@ -191,11 +189,6 @@
Parameters
- - - - - @@ -204,7 +197,7 @@
Parameters
data
intoffset
int length
Overrides
- + @@ -314,7 +307,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.Layers.html b/docs/api/LiteNetLib.Layers.html index b9224a06..573749d7 100644 --- a/docs/api/LiteNetLib.Layers.html +++ b/docs/api/LiteNetLib.Layers.html @@ -1,15 +1,14 @@ - + - Namespace LiteNetLib.Layers - + Namespace LiteNetLib.Layers - + + @@ -69,12 +68,12 @@
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 7f4ccb85..e6088603 100644 --- a/docs/api/LiteNetLib.LocalAddrType.html +++ b/docs/api/LiteNetLib.LocalAddrType.html @@ -1,16 +1,15 @@ - + - Enum LocalAddrType - + Enum LocalAddrType - + + @@ -72,8 +71,7 @@ -

Enum LocalAddrType -

+

Enum LocalAddrType

Address type that you want to receive from NetUtils.GetLocalIp method

@@ -93,7 +91,7 @@

Fields Description - + All @@ -107,7 +105,7 @@

Fields - +

@@ -118,7 +116,7 @@

Fields

diff --git a/docs/api/LiteNetLib.NatAddressType.html b/docs/api/LiteNetLib.NatAddressType.html index 3a72ed78..feae78d5 100644 --- a/docs/api/LiteNetLib.NatAddressType.html +++ b/docs/api/LiteNetLib.NatAddressType.html @@ -1,16 +1,15 @@ - + - Enum NatAddressType - + Enum NatAddressType - + + @@ -72,9 +71,9 @@ -

Enum NatAddressType -

-
+

Enum NatAddressType

+

Specifies the type of network address discovered during NAT punchthrough.

+
Namespace: LiteNetLib
Assembly: LiteNetLib.dll
@@ -91,17 +90,19 @@

Fields Description - + External - +

Publicly accessible address on the wide area network (WAN).

+ Internal - +

Address within the local area network (LAN).

+ - + @@ -112,7 +113,7 @@

Fields diff --git a/docs/api/LiteNetLib.NatPunchModule.html b/docs/api/LiteNetLib.NatPunchModule.html index d6f0eb40..a789e407 100644 --- a/docs/api/LiteNetLib.NatPunchModule.html +++ b/docs/api/LiteNetLib.NatPunchModule.html @@ -1,16 +1,15 @@ - + - Class NatPunchModule - + Class NatPunchModule - + + @@ -71,14 +70,13 @@ -

Class NatPunchModule -

+

Class NatPunchModule

Module for UDP NAT Hole punching operations. Can be accessed from NetManager

Inheritance
- +
object
NatPunchModule
Namespace: LiteNetLib
@@ -92,7 +90,8 @@

Fields

MaxTokenLength

-
+

Maximum allowed length for the NAT introduction token string.

+
Declaration
@@ -144,7 +143,8 @@

Methods

Init(INatPunchListener)

-
+

Initializes the NAT punch module with a listener to handle punchthrough events.

+
Declaration
@@ -163,7 +163,8 @@
Parameters
INatPunchListener listener - +

The listener implementation that will receive NAT events.

+ @@ -171,7 +172,8 @@
Parameters

NatIntroduce(IPEndPoint, IPEndPoint, IPEndPoint, IPEndPoint, string)

-
+

Sends NAT introduction packets to both the host and the client to facilitate punchthrough.

+
Declaration
@@ -190,45 +192,58 @@
Parameters
IPEndPoint hostInternal - +

Internal (LAN) endpoint of the host.

+ IPEndPoint hostExternal - +

External (WAN) endpoint of the host.

+ IPEndPoint clientInternal - +

Internal (LAN) endpoint of the client.

+ IPEndPoint clientExternal - +

External (WAN) endpoint of the client.

+ string additionalInfo - +

Custom token or data to include in the introduction.

+ +
Remarks
+

This is typically called by a mediator (e.g. a master server).

+

PollEvents()

-
+

Triggers queued NAT punchthrough events (Success or Request) on the provided INatPunchListener.

+
Declaration
public void PollEvents()
+
Remarks
+

This should be called from the main thread if UnsyncedEvents is false.

+

SendNatIntroduceRequest(IPEndPoint, string)

-
+

Sends a request to the Master Server to introduce this peer to another peer.

+
Declaration
@@ -247,12 +262,14 @@
Parameters
IPEndPoint masterServerEndPoint - +

The endpoint of the Master Server.

+ string additionalInfo - +

Custom token to identify the connection or room.

+ @@ -260,7 +277,8 @@
Parameters

SendNatIntroduceRequest(string, int, string)

-
+

Sends a request to the Master Server to introduce this peer to another peer.

+
Declaration
@@ -279,17 +297,20 @@
Parameters
string host - +

The hostname or IP of the Master Server.

+ int port - +

The port of the Master Server.

+ string additionalInfo - +

Custom token to identify the connection or room.

+ @@ -304,7 +325,7 @@
Parameters
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 67a78ace..e89a2a94 100644 --- a/docs/api/LiteNetLib.NetConstants.html +++ b/docs/api/LiteNetLib.NetConstants.html @@ -1,16 +1,15 @@ - + - Class NetConstants - + Class NetConstants - + + @@ -71,14 +70,13 @@ -

Class NetConstants -

+

Class NetConstants

Network constants. Can be tuned from sources for your purposes.

Inheritance
- +
object
NetConstants
Namespace: LiteNetLib
@@ -92,7 +90,9 @@

Fields

ChanneledHeaderSize

-
+

Size of the header for sequenced or reliable messages in bytes.
+Includes HeaderSize, Sequence, and ChannelId.

+
Declaration
@@ -116,7 +116,8 @@
Field Value

DefaultWindowSize

-
+

Default window size for reliable channels (number of packets).

+
Declaration
@@ -139,12 +140,14 @@
Field Value
-

FragmentedHeaderTotalSize

-
+

FragmentHeaderSize

+

Additional header size required for fragmented packets in bytes.
+Includes FragmentId, FragmentPart, and FragmentsTotal.

+
Declaration
-
public const int FragmentedHeaderTotalSize = 10
+
public const int FragmentHeaderSize = 6
Field Value
@@ -163,12 +166,14 @@
Field Value
-

FragmentHeaderSize

-
+

FragmentedHeaderTotalSize

+

Total header size for a fragmented channeled packet in bytes.
+Combines ChanneledHeaderSize and FragmentHeaderSize.

+
Declaration
-
public const int FragmentHeaderSize = 6
+
public const int FragmentedHeaderTotalSize = 10
Field Value
@@ -188,7 +193,8 @@
Field Value

HalfMaxSequence

-
+

Half of the MaxSequence, used for sequence comparison and wrap-around logic.

+
Declaration
@@ -212,7 +218,8 @@
Field Value

HeaderSize

-
+

Size of the base packet header (PacketProperty) in bytes.

+
Declaration
@@ -235,8 +242,34 @@
Field Value
+

InitialMtu

+

The starting Maximum Transmission Unit (MTU) used for new connections before path MTU discovery.

+
+
+
Declaration
+
+
public static readonly int InitialMtu
+
+
Field Value
+ + + + + + + + + + + + + +
TypeDescription
int
+ +

MaxConnectionNumber

-
+

Maximum possible value for LiteNetLib.NetPacket.ConnectionNumber.

+
Declaration
@@ -257,10 +290,16 @@
Field Value
+
Remarks
+

This value is used to distinguish between different connection instances from the same IPEndPoint.
+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.

+

MaxPacketSize

-
+

Maximum possible packet size allowed by the library based on the largest supported MTU.

+
Declaration
@@ -284,7 +323,8 @@
Field Value

MaxSequence

-
+

Maximum possible sequence number before wrapping back to zero.

+
Declaration
@@ -308,7 +348,9 @@
Field Value

MaxUnreliableDataSize

-
+

Maximum payload size for a single unreliable packet in bytes.
+Calculated as MaxPacketSize - HeaderSize.

+
Declaration
@@ -332,7 +374,9 @@
Field Value

SocketBufferSize

-
+

Size of the underlying UDP socket receive and send buffers in bytes.
+Default is 1MB.

+
Declaration
@@ -356,7 +400,8 @@
Field Value

SocketTTL

-
+

Time To Live (TTL) for the UDP packets.

+
Declaration
@@ -388,7 +433,7 @@
Field Value
diff --git a/docs/api/LiteNetLib.NetDebug.html b/docs/api/LiteNetLib.NetDebug.html index e89dfa84..f6d47a40 100644 --- a/docs/api/LiteNetLib.NetDebug.html +++ b/docs/api/LiteNetLib.NetDebug.html @@ -1,16 +1,15 @@ - + - Class NetDebug - + Class NetDebug - + + @@ -71,15 +70,14 @@ -

Class NetDebug -

+

Class NetDebug

Static class for defining your own LiteNetLib logger instead of Console.WriteLine or Debug.Log if compiled with UNITY flag

Inheritance
- +
object
NetDebug
Namespace: LiteNetLib
@@ -125,7 +123,7 @@
Field Value
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 db1fd024..b8decbe7 100644 --- a/docs/api/LiteNetLib.NetLogLevel.html +++ b/docs/api/LiteNetLib.NetLogLevel.html @@ -1,15 +1,14 @@ - + - Enum NetLogLevel - + Enum NetLogLevel - + + @@ -72,8 +71,7 @@ -

Enum NetLogLevel -

+

Enum NetLogLevel

Namespace: LiteNetLib
@@ -91,7 +89,7 @@

Fields Description - + Error @@ -109,7 +107,7 @@

Fields - +

@@ -120,7 +118,7 @@

Fields

diff --git a/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html b/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html index fcb2c4d9..3050dfb0 100644 --- a/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html +++ b/docs/api/LiteNetLib.NetManager.NetPeerEnumerator.html @@ -1,15 +1,14 @@ - + - Struct NetManager.NetPeerEnumerator - + Struct NetManager.NetPeerEnumerator - + + @@ -71,8 +70,7 @@ -

Struct NetManager.NetPeerEnumerator -

+

Struct NetManager.NetPeerEnumerator

@@ -122,7 +120,8 @@

Properties

Current

-
+

Gets the element in the collection at the current position of the enumerator.

+
Declaration
@@ -139,7 +138,8 @@
Property Value
NetPeer - +

The element in the collection at the current position of the enumerator.

+ @@ -160,7 +160,8 @@
Declaration

MoveNext()

-
+

Advances the enumerator to the next element of the collection.

+
Declaration
@@ -177,7 +178,24 @@
Returns
bool - +

true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.

+ + + + +
Exceptions
+ + + + + + + + + + +
TypeCondition
InvalidOperationException

The collection was modified after the enumerator was created.

+
@@ -185,12 +203,29 @@
Returns

Reset()

-
+

Sets the enumerator to its initial position, which is before the first element in the collection.

+
Declaration
public void Reset()
+
Exceptions
+ + + + + + + + + + + + + +
TypeCondition
InvalidOperationException

The collection was modified after the enumerator was created.

+

Implements

IEnumerator<T> @@ -212,7 +247,7 @@

Implements

diff --git a/docs/api/LiteNetLib.NetManager.html b/docs/api/LiteNetLib.NetManager.html index 52c330bc..4e240dbe 100644 --- a/docs/api/LiteNetLib.NetManager.html +++ b/docs/api/LiteNetLib.NetManager.html @@ -1,16 +1,15 @@ - + - Class NetManager - + Class NetManager - + + @@ -71,26 +70,288 @@ -

Class NetManager -

-

Main class for all network operations. Can be used as client and/or server.

+

Class NetManager

+

More feature rich network manager with adjustable channels count

Inheritance
- -
NetManager
+
object
+ +
NetManager
+
+
Inherited Members
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Namespace: LiteNetLib
Assembly: LiteNetLib.dll
Syntax
-
public class NetManager : IEnumerable<NetPeer>, IEnumerable
+
public class NetManager : LiteNetManager, IEnumerable<LiteNetPeer>, IEnumerable<NetPeer>, IEnumerable

Constructors

@@ -98,8 +359,7 @@

Constructors

NetManager(INetEventListener, PacketLayerBase)

-

NetManager constructor

-
+
Declaration
@@ -118,30 +378,29 @@
Parameters
INetEventListener listener -

Network events listener (also can implement IDeliveryEventListener)

- + PacketLayerBase extraPacketLayer -

Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer.

- + -

Fields +

Properties

-

AllowPeerAddressChange

-

Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server

+ +

ChannelsCount

+

QoS channel count per message type (value must be between 1 and 64 channels)

Declaration
-
public bool AllowPeerAddressChange
+
public byte ChannelsCount { get; set; }
-
Field Value
+
Property Value
@@ -151,22 +410,23 @@
Field Value
- +
boolbyte
-

AutoRecycle

-

Automatically recycle NetPacketReader after OnReceive event

+ +

FirstPeer

+

First peer. Useful for Client mode

Declaration
-
public bool AutoRecycle
+
public NetPeer FirstPeer { get; }
-
Field Value
+
Property Value
@@ -176,73 +436,49 @@
Field Value
- +
boolNetPeer
+

Methods +

-

BroadcastReceiveEnabled

-

Allows receive broadcast packets

+ +

Connect(IPEndPoint, NetDataWriter)

+

Connect to remote host

Declaration
-
public bool BroadcastReceiveEnabled
+
public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
-
Field Value
+
Parameters
+ - - - - -
TypeName Description
bool
- - -

DisconnectOnUnreachable

-

Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true)

-
-
-
Declaration
-
-
public bool DisconnectOnUnreachable
-
-
Field Value
- - - - - + + + - - - - + + +
TypeDescriptionIPEndPointtarget

Server end point (ip and port)

+
boolNetDataWriterconnectionData

Additional data for remote peer

+
- - -

DisconnectTimeout

-

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)

-
-
-
Declaration
-
-
public int DisconnectTimeout
-
-
Field Value
+
Returns
@@ -252,121 +488,64 @@
Field Value
- - + +
intNetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

+
- - -

EnableStatistics

-

Toggles the collection of network statistics for the instance and all known peers

-
-
-
Declaration
-
-
public bool EnableStatistics
-
-
Field Value
+
Exceptions
- + - - + +
TypeDescriptionCondition
boolInvalidOperationException

Manager is not running. Call Start()

+
-

IPv6Enabled

-

IPv6 support

+ +

Connect(IPEndPoint, ReadOnlySpan<byte>)

+

Connect to remote host

Declaration
-
public bool IPv6Enabled
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

IPv6Support

-
-
-
Declaration
-
-
public static readonly bool IPv6Support
+
public NetPeer Connect(IPEndPoint target, ReadOnlySpan<byte> connectionData)
-
Field Value
+
Parameters
+ - - - - -
TypeName Description
bool
- - -

MaxConnectAttempts

-

Maximum connection attempts before client stops and call disconnect event.

-
-
-
Declaration
-
-
public int MaxConnectAttempts
-
-
Field Value
- - - - - + + + - - - - + + +
TypeDescriptionIPEndPointtarget

Server end point (ip and port)

+
intReadOnlySpan<byte>connectionData

Additional data for remote peer

+
- - -

MaxPacketsReceivePerUpdate

-

Maximum packets count that will be processed in Manual PollEvents

-
-
-
Declaration
-
-
public int MaxPacketsReceivePerUpdate
-
-
Field Value
+
Returns
@@ -376,2129 +555,38 @@
Field Value
- - + +
intNetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

+
- - -

MtuOverride

-

Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery!

-
-
-
Declaration
-
-
public int MtuOverride
-
-
Field Value
+
Exceptions
- + - - - - - -
TypeDescriptionCondition
int
- - -

NatPunchEnabled

-

Enable nat punch messages

-
-
-
Declaration
-
-
public bool NatPunchEnabled
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

NatPunchModule

-

NatPunchModule for NAT hole punching operations

-
-
-
Declaration
-
-
public readonly NatPunchModule NatPunchModule
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
NatPunchModule
- - -

PacketPoolSize

-

Maximum packet pool size (increase if you have tons of packets sending)

-
-
-
Declaration
-
-
public int PacketPoolSize
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
int
- - -

PingInterval

-

Interval for latency detection and checking connection (in milliseconds)

-
-
-
Declaration
-
-
public int PingInterval
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
int
- - -

ReconnectDelay

-

Delay between initial connection attempts (in milliseconds)

-
-
-
Declaration
-
-
public int ReconnectDelay
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
int
- - -

ReuseAddress

-

Enables socket option "ReuseAddress" for specific purposes

-
-
-
Declaration
-
-
public bool ReuseAddress
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

SimulateLatency

-

Simulate latency by holding packets for random time. (Works only in DEBUG mode)

-
-
-
Declaration
-
-
public bool SimulateLatency
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

SimulatePacketLoss

-

Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode)

-
-
-
Declaration
-
-
public bool SimulatePacketLoss
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

SimulationMaxLatency

-

Maximum simulated latency (in milliseconds)

-
-
-
Declaration
-
-
public int SimulationMaxLatency
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
int
- - -

SimulationMinLatency

-

Minimum simulated latency (in milliseconds)

-
-
-
Declaration
-
-
public int SimulationMinLatency
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
int
- - -

SimulationPacketLossChance

-

Chance of packet loss when simulation enabled. value in percents (1 - 100).

-
-
-
Declaration
-
-
public int SimulationPacketLossChance
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
int
- - -

Statistics

-

Statistics of all connections

-
-
-
Declaration
-
-
public readonly NetStatistics Statistics
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
NetStatistics
- - -

UnconnectedMessagesEnabled

-

Enable messages receiving without connection. (with SendUnconnectedMessage method)

-
-
-
Declaration
-
-
public bool UnconnectedMessagesEnabled
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

UnsyncedDeliveryEvent

-

If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call

-
-
-
Declaration
-
-
public bool UnsyncedDeliveryEvent
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

UnsyncedEvents

-

Events automatically will be called without PollEvents method from another thread

-
-
-
Declaration
-
-
public bool UnsyncedEvents
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

UnsyncedReceiveEvent

-

If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call

-
-
-
Declaration
-
-
public bool UnsyncedReceiveEvent
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

UpdateTime

-

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 TriggerUpdate()

-
-
-
Declaration
-
-
public int UpdateTime
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
int
- - -

UseNativeSockets

-

Experimental feature mostly for servers. Only for Windows/Linux -use direct socket calls for send/receive to drastically increase speed and reduce GC pressure

-
-
-
Declaration
-
-
public bool UseNativeSockets
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - -

UseSafeMtu

-

Sets initial MTU to lowest possible value according to RFC1191 (576 bytes)

-
-
-
Declaration
-
-
public bool UseSafeMtu
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
bool
-

Properties -

- - - -

ChannelsCount

-

QoS channel count per message type (value must be between 1 and 64 channels)

-
-
-
Declaration
-
-
public byte ChannelsCount { get; set; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
byte
- - - -

ConnectedPeerList

-

Returns connected peers list (with internal cached list)

-
-
-
Declaration
-
-
public List<NetPeer> ConnectedPeerList { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
List<NetPeer>
- - - -

ConnectedPeersCount

-

Returns connected peers count

-
-
-
Declaration
-
-
public int ConnectedPeersCount { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

ExtraPacketSizeForLayer

-
-
-
Declaration
-
-
public int ExtraPacketSizeForLayer { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

FirstPeer

-

First peer. Useful for Client mode

-
-
-
Declaration
-
-
public NetPeer FirstPeer { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
NetPeer
- - - -

IsRunning

-

Returns true if socket listening and update thread is running

-
-
-
Declaration
-
-
public bool IsRunning { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
bool
- - - -

LocalPort

-

Local EndPoint (host and port)

-
-
-
Declaration
-
-
public int LocalPort { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

PoolCount

-
-
-
Declaration
-
-
public int PoolCount { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

Ttl

-
-
-
Declaration
-
-
public short Ttl { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
short
-

Methods -

- - - -

Connect(IPEndPoint, NetDataWriter)

-

Connect to remote host

-
-
-
Declaration
-
-
public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
IPEndPointtarget

Server end point (ip and port)

-
NetDataWriterconnectionData

Additional data for remote peer

-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
NetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

-
-
Exceptions
- - - - - - - - - - - - - -
TypeCondition
InvalidOperationException

Manager is not running. Call Start()

-
- - - -

Connect(IPEndPoint, string)

-

Connect to remote host

-
-
-
Declaration
-
-
public NetPeer Connect(IPEndPoint target, string key)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
IPEndPointtarget

Server end point (ip and port)

-
stringkey

Connection key

-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
NetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

-
-
Exceptions
- - - - - - - - - - - - - -
TypeCondition
InvalidOperationException

Manager is not running. Call Start()

-
- - - -

Connect(string, int, NetDataWriter)

-

Connect to remote host

-
-
-
Declaration
-
-
public NetPeer Connect(string address, int port, NetDataWriter connectionData)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
stringaddress

Server IP or hostname

-
intport

Server Port

-
NetDataWriterconnectionData

Additional data for remote peer

-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
NetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

-
-
Exceptions
- - - - - - - - - - - - - -
TypeCondition
InvalidOperationException

Manager is not running. Call Start()

-
- - - -

Connect(string, int, string)

-

Connect to remote host

-
-
-
Declaration
-
-
public NetPeer Connect(string address, int port, string key)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
stringaddress

Server IP or hostname

-
intport

Server Port

-
stringkey

Connection key

-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
NetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

-
-
Exceptions
- - - - - - - - - - - - - -
TypeCondition
InvalidOperationException

Manager is not running. Call Start()

-
- - - -

CreateNtpRequest(IPEndPoint)

-

Create the requests for NTP server

-
-
-
Declaration
-
-
public void CreateNtpRequest(IPEndPoint endPoint)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
IPEndPointendPoint

NTP Server address.

-
- - - -

CreateNtpRequest(string, int)

-

Create the requests for NTP server

-
-
-
Declaration
-
-
public void CreateNtpRequest(string ntpServerAddress, int port)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
stringntpServerAddress

NTP Server address.

-
intport

port

-
- - - -

CreateNtpRequest(string)

-

Create the requests for NTP server (default port)

-
-
-
Declaration
-
-
public void CreateNtpRequest(string ntpServerAddress)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
stringntpServerAddress

NTP Server address.

-
- - - -

DisconnectAll()

-

Disconnect all peers without any additional data

-
-
-
Declaration
-
-
public void DisconnectAll()
-
- - - -

DisconnectAll(byte[], int, int)

-

Disconnect all peers with shutdown message

-
-
-
Declaration
-
-
public void DisconnectAll(byte[] data, int start, int count)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data to send (must be less or equal MTU)

-
intstart

Data start

-
intcount

Data count

-
- - - -

DisconnectPeer(NetPeer, NetDataWriter)

-

Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)

-
-
-
Declaration
-
-
public void DisconnectPeer(NetPeer peer, NetDataWriter writer)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetPeerpeer

peer to disconnect

-
NetDataWriterwriter

additional data

-
- - - -

DisconnectPeer(NetPeer, byte[], int, int)

-

Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)

-
-
-
Declaration
-
-
public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetPeerpeer

peer to disconnect

-
byte[]data

additional data

-
intstart

data start

-
intcount

data length

-
- - - -

DisconnectPeer(NetPeer, byte[])

-

Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)

-
-
-
Declaration
-
-
public void DisconnectPeer(NetPeer peer, byte[] data)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetPeerpeer

peer to disconnect

-
byte[]data

additional data

-
- - - -

DisconnectPeer(NetPeer)

-

Disconnect peer from server

-
-
-
Declaration
-
-
public void DisconnectPeer(NetPeer peer)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
NetPeerpeer

peer to disconnect

-
- - - -

DisconnectPeerForce(NetPeer)

-

Immediately disconnect peer from server without additional data

-
-
-
Declaration
-
-
public void DisconnectPeerForce(NetPeer peer)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
NetPeerpeer

peer to disconnect

-
- - - -

GetEnumerator()

-
-
-
Declaration
-
-
public NetManager.NetPeerEnumerator GetEnumerator()
-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
NetManager.NetPeerEnumerator
- - - -

GetPeerById(int)

-

Gets peer by peer id

-
-
-
Declaration
-
-
public NetPeer GetPeerById(int id)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
intid

id of peer

-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
NetPeer

Peer if peer with id exist, otherwise null

-
- - - -

GetPeersCount(ConnectionState)

-

Return peers count with connection state

-
-
-
Declaration
-
-
public int GetPeersCount(ConnectionState peerState)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
ConnectionStatepeerState

peer connection state (you can use as bit flags)

-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
int

peers count

-
- - - -

GetPeersNonAlloc(List<NetPeer>, ConnectionState)

-

Get copy of peers (without allocations)

-
-
-
Declaration
-
-
public void GetPeersNonAlloc(List<NetPeer> peers, ConnectionState peerState)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
List<NetPeer>peers

List that will contain result

-
ConnectionStatepeerState

State of peers

-
- - - -

ManualUpdate(int)

-

Update and send logic. Use this only when NetManager started in manual mode

-
-
-
Declaration
-
-
public void ManualUpdate(int elapsedMilliseconds)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
intelapsedMilliseconds

elapsed milliseconds since last update call

-
- - - -

PollEvents()

-

Receive all pending events. Call this in game update code -In Manual mode it will call also socket Receive (which can be slow)

-
-
-
Declaration
-
-
public void PollEvents()
-
- - - -

SendBroadcast(NetDataWriter, int)

-
-
-
Declaration
-
-
public bool SendBroadcast(NetDataWriter writer, int port)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterwriter
intport
-
Returns
- - - - - - - - - - - - - -
TypeDescription
bool
- - - -

SendBroadcast(byte[], int, int, int)

-
-
-
Declaration
-
-
public bool SendBroadcast(byte[] data, int start, int length, int port)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data
intstart
intlength
intport
-
Returns
- - - - - - - - - - - - - -
TypeDescription
bool
- - - -

SendBroadcast(byte[], int)

-
-
-
Declaration
-
-
public bool SendBroadcast(byte[] data, int port)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data
intport
-
Returns
- - - - - - - - - - - - - -
TypeDescription
bool
- - - -

SendToAll(NetDataWriter, DeliveryMethod, NetPeer)

-

Send data to all connected peers (channel - 0)

-
-
-
Declaration
-
-
public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterwriter

DataWriter with data

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
NetPeerexcludePeer

Excluded peer

-
- - - -

SendToAll(NetDataWriter, DeliveryMethod)

-

Send data to all connected peers (channel - 0)

-
-
-
Declaration
-
-
public void SendToAll(NetDataWriter writer, DeliveryMethod options)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterwriter

DataWriter with data

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
- - - -

SendToAll(NetDataWriter, byte, DeliveryMethod, NetPeer)

-

Send data to all connected peers

-
-
-
Declaration
-
-
public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterwriter

DataWriter with data

-
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
NetPeerexcludePeer

Excluded peer

-
- - - -

SendToAll(NetDataWriter, byte, DeliveryMethod)

-

Send data to all connected peers

-
-
-
Declaration
-
-
public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterwriter

DataWriter with data

-
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
- - - -

SendToAll(byte[], DeliveryMethod, NetPeer)

-

Send data to all connected peers (channel - 0)

-
-
-
Declaration
-
-
public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
NetPeerexcludePeer

Excluded peer

-
- - - -

SendToAll(byte[], DeliveryMethod)

-

Send data to all connected peers (channel - 0)

-
-
-
Declaration
-
-
public void SendToAll(byte[] data, DeliveryMethod options)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
- - - -

SendToAll(byte[], byte, DeliveryMethod, NetPeer)

-

Send data to all connected peers

-
-
-
Declaration
-
-
public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data

-
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
NetPeerexcludePeer

Excluded peer

-
- - - -

SendToAll(byte[], byte, DeliveryMethod)

-

Send data to all connected peers

-
-
-
Declaration
-
-
public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data

-
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
- - - -

SendToAll(byte[], int, int, DeliveryMethod, NetPeer)

-

Send data to all connected peers (channel - 0)

-
-
-
Declaration
-
-
public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data

-
intstart

Start of data

-
intlength

Length of data

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
NetPeerexcludePeer

Excluded peer

-
- - - -

SendToAll(byte[], int, int, DeliveryMethod)

-

Send data to all connected peers (channel - 0)

-
-
-
Declaration
-
-
public void SendToAll(byte[] data, int start, int length, DeliveryMethod options)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
TypeNameDescription
byte[]data

Data

-
intstart

Start of data

-
intlength

Length of data

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

+
InvalidOperationException

Manager is not running. Call Start()

- -

SendToAll(byte[], int, int, byte, DeliveryMethod, NetPeer)

-

Send data to all connected peers

+ +

Connect(IPEndPoint, string)

+

Connect to remote host

Declaration
-
public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
+
public NetPeer Connect(IPEndPoint target, string key)
Parameters
@@ -2511,53 +599,61 @@
Parameters
- - - + + - - - + + + +
byte[]data

Data

+
IPEndPointtarget

Server end point (ip and port)

intstart

Start of data

+
stringkey

Connection key

+
Returns
+ + - - - + + + + - - - + + +
intlength

Length of data

-
TypeDescription
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

+
NetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

+
Exceptions
+ + - - - + + + + - - - +
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
TypeCondition
NetPeerexcludePeer

Excluded peer

+
InvalidOperationException

Manager is not running. Call Start()

- -

SendToAll(byte[], int, int, byte, DeliveryMethod)

-

Send data to all connected peers

+ +

Connect(string, int, NetDataWriter)

+

Connect to remote host

Declaration
-
public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options)
+
public NetPeer Connect(string address, int port, NetDataWriter connectionData)
Parameters
@@ -2570,100 +666,67 @@
Parameters
- - - - - - - - + + - - - - - - - + - - - + +
byte[]data

Data

-
intstart

Start of data

+
stringaddress

Server IP or hostname

intlength

Length of data

-
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

+
port

Server Port

DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

+
NetDataWriterconnectionData

Additional data for remote peer

- - - -

SendUnconnectedMessage(NetDataWriter, IPEndPoint)

-

Send message without connection

-
-
-
Declaration
-
-
public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint)
-
-
Parameters
+
Returns
- - - - - - - - - +
TypeName Description
NetDataWriterwriter

Data serializer

-
IPEndPointremoteEndPoint

Packet destination

+
NetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

-
Returns
+
Exceptions
- + - - +
TypeDescriptionCondition
bool

Operation result

+
InvalidOperationException

Manager is not running. Call Start()

- -

SendUnconnectedMessage(NetDataWriter, string, int)

-

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.

+ +

Connect(string, int, string)

+

Connect to remote host

Declaration
-
public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port)
+
public NetPeer Connect(string address, int port, string key)
Parameters
@@ -2675,22 +738,22 @@
Parameters
- - - - - - - + + + + + @@ -2705,22 +768,37 @@
Returns
- - + + + +
NetDataWriterwriter

Data serializer

-
string address

Packet destination IP or hostname

+

Server IP or hostname

int port

Packet destination port

+

Server Port

+
stringkey

Connection key

bool

Operation result

+
NetPeer

New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting

+
+
Exceptions
+ + + + + + + + + + +
TypeCondition
InvalidOperationException

Manager is not running. Call Start()

- -

SendUnconnectedMessage(byte[], int, int, IPEndPoint)

-

Send message without connection

-
+ +

CreateConnectionRequest(IPEndPoint, NetConnectRequestPacket)

+
Declaration
-
public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
+
protected override LiteConnectionRequest CreateConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket)
Parameters
@@ -2732,29 +810,15 @@
Parameters
- - - - - - - - - - - - - - - - + + + + + +
byte[]message

Raw data

-
intstart

data start

-
intlength

data length

-
IPEndPoint remoteEndPoint

Packet destination

-
NetConnectRequestPacketrequestPacket
@@ -2768,22 +832,22 @@
Returns
- bool -

Operation result

- + LiteConnectionRequest + +
Overrides
+ - -

SendUnconnectedMessage(byte[], IPEndPoint)

-

Send message without connection

-
+ +

CreateIncomingPeer(LiteConnectionRequest, int)

+
Declaration
-
public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint)
+
protected override LiteNetPeer CreateIncomingPeer(LiteConnectionRequest request, int id)
Parameters
@@ -2796,16 +860,14 @@
Parameters
- - - + + + - - - + + +
byte[]message

Raw data

-
LiteConnectionRequestrequest
IPEndPointremoteEndPoint

Packet destination

-
intid
@@ -2819,21 +881,23 @@
Returns
- bool -

Operation result

- + LiteNetPeer + +
Overrides
+ - -

SocketActive(bool)

-
+ +

CreateNtpRequest(IPEndPoint)

+

Create the requests for NTP server

+
Declaration
-
public bool SocketActive(bool ipv4)
+
public void CreateNtpRequest(IPEndPoint endPoint)
Parameters
@@ -2846,63 +910,86 @@
Parameters
- - - + + +
boolipv4IPEndPointendPoint

NTP Server address.

+
-
Returns
+ + + +

CreateNtpRequest(string)

+

Create the requests for NTP server (default port)

+
+
+
Declaration
+
+
public void CreateNtpRequest(string ntpServerAddress)
+
+
Parameters
+ - - + + +
TypeName Description
boolstringntpServerAddress

NTP Server address.

+
- -

Start()

-

Start logic thread and listening on available port

+ +

CreateNtpRequest(string, int)

+

Create the requests for NTP server

Declaration
-
public bool Start()
+
public void CreateNtpRequest(string ntpServerAddress, int port)
-
Returns
+
Parameters
+ - - + + + + + + + +
TypeName Description
boolstringntpServerAddress

NTP Server address.

+
intport

port

+
- -

Start(int)

-

Start logic thread and listening on selected port

-
+ +

CreateOutgoingPeer(IPEndPoint, int, byte, ReadOnlySpan<byte>)

+
Declaration
-
public bool Start(int port)
+
protected override LiteNetPeer CreateOutgoingPeer(IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan<byte> connectData)
Parameters
@@ -2914,11 +1001,25 @@
Parameters
+ + + + + - - + + + + + + + + + + + +
IPEndPointremoteEndPoint
intport

port to listen

-
id
byteconnectNum
ReadOnlySpan<byte>connectData
@@ -2932,21 +1033,22 @@
Returns
- bool + LiteNetPeer +
Overrides
+ - -

Start(IPAddress, IPAddress, int, bool)

-

Start logic thread and listening on selected port

-
+ +

CreateRejectPeer(IPEndPoint, int)

+
Declaration
-
public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode)
+
protected override LiteNetPeer CreateRejectPeer(IPEndPoint remoteEndPoint, int id)
Parameters
@@ -2959,28 +1061,14 @@
Parameters
- - - - - - - - + + + - - - - - - - + +
IPAddressaddressIPv4

bind to specific ipv4 address

-
IPAddressaddressIPv6

bind to specific ipv6 address

-
IPEndPointremoteEndPoint
intport

port to listen

-
boolmanualMode

mode of library

-
id
@@ -2994,21 +1082,23 @@
Returns
- bool + LiteNetPeer +
Overrides
+ - -

Start(IPAddress, IPAddress, int)

-

Start logic thread and listening on selected port

+ +

GetConnectedPeers(List<NetPeer>)

+

Get copy of connected peers (without allocations)

Declaration
-
public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port)
+
public void GetConnectedPeers(List<NetPeer> peers)
Parameters
@@ -3021,25 +1111,23 @@
Parameters
- - - - - - - - - - - - - + +
IPAddressaddressIPv4

bind to specific ipv4 address

-
IPAddressaddressIPv6

bind to specific ipv6 address

-
intport

port to listen

+
List<NetPeer>peers

List that will contain result

+ + + +

GetEnumerator()

+
+
+
Declaration
+
+
public LiteNetManager.NetPeerEnumerator<NetPeer> GetEnumerator()
+
Returns
@@ -3050,21 +1138,21 @@
Returns
- +
boolLiteNetManager.NetPeerEnumerator<NetPeer>
- -

Start(string, string, int)

-

Start logic thread and listening on selected port

+ +

GetPeers(List<NetPeer>, ConnectionState)

+

Get copy of peers (without allocations)

Declaration
-
public bool Start(string addressIPv4, string addressIPv6, int port)
+
public void GetPeers(List<NetPeer> peers, ConnectionState peerState)
Parameters
@@ -3077,53 +1165,57 @@
Parameters
- - - - - - - - + + - - - + +
stringaddressIPv4

bind to specific ipv4 address

-
stringaddressIPv6

bind to specific ipv6 address

+
List<NetPeer>peers

List that will contain result

intport

port to listen

+
ConnectionStatepeerState

State of peers

-
Returns
+ + + +

ProcessEvent(NetEvent)

+
+
+
Declaration
+
+
protected override void ProcessEvent(NetEvent evt)
+
+
Parameters
+ - + +
TypeName Description
boolNetEventevt
+
Overrides
+ - -

StartInManualMode(int)

-

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

-
+ +

ProcessNtpRequests(float)

+
Declaration
-
public bool StartInManualMode(int port)
+
protected override void ProcessNtpRequests(float elapsedMilliseconds)
Parameters
@@ -3136,41 +1228,65 @@
Parameters
- - - + + +
intport

port to listen

-
floatelapsedMilliseconds
-
Returns
+
Overrides
+ + + + +

SendToAll(NetDataWriter, byte, DeliveryMethod)

+

Send data to all connected peers

+
+
+
Declaration
+
+
public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options)
+
+
Parameters
+ - - + + + + + + + + + + + + +
TypeName Description
boolNetDataWriterwriter

DataWriter with data

+
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

+
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

+
- -

StartInManualMode(IPAddress, IPAddress, int)

-

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

+ +

SendToAll(NetDataWriter, byte, DeliveryMethod, LiteNetPeer)

+

Send data to all connected peers

Declaration
-
public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port)
+
public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer)
Parameters
@@ -3183,53 +1299,41 @@
Parameters
- - - + + - - - + + - - - + + - -
IPAddressaddressIPv4

bind to specific ipv4 address

+
NetDataWriterwriter

DataWriter with data

IPAddressaddressIPv6

bind to specific ipv6 address

+
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

intport

port to listen

+
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
Returns
- - - - - - - - - - + + +
TypeDescription
boolLiteNetPeerexcludePeer

Excluded peer

+
- -

StartInManualMode(string, string, int)

-

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

+ +

SendToAll(byte[], byte, DeliveryMethod)

+

Send data to all connected peers

Declaration
-
public bool StartInManualMode(string addressIPv4, string addressIPv6, int port)
+
public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options)
Parameters
@@ -3242,61 +1346,82 @@
Parameters
- - - + + - - - + + - - - + +
stringaddressIPv4

bind to specific ipv4 address

+
byte[]data

Data

stringaddressIPv6

bind to specific ipv6 address

+
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

intport

port to listen

+
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
Returns
+ + + +

SendToAll(byte[], byte, DeliveryMethod, LiteNetPeer)

+

Send data to all connected peers

+
+
+
Declaration
+
+
public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer)
+
+
Parameters
+ - - + + + + + + + + + + + + + + + + + +
TypeName Description
boolbyte[]data

Data

+
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

+
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

+
LiteNetPeerexcludePeer

Excluded peer

+
- -

Stop()

-

Force closes connection and stop all threads.

-
-
-
Declaration
-
-
public void Stop()
-
- - - -

Stop(bool)

-

Force closes connection and stop all threads.

+ +

SendToAll(byte[], int, int, byte, DeliveryMethod)

+

Send data to all connected peers

Declaration
-
public void Stop(bool sendDisconnectMessages)
+
public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options)
Parameters
@@ -3309,34 +1434,47 @@
Parameters
- - - + + + + + + + + + + + + + + + + + + + + + +
boolsendDisconnectMessages

Send disconnect messages

+
byte[]data

Data

+
intstart

Start of data

+
intlength

Length of data

+
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

+
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

- -

TriggerUpdate()

-

Triggers update and send logic immediately (works asynchronously)

-
-
-
Declaration
-
-
public void TriggerUpdate()
-
- - - -

TryGetPeerById(int, out NetPeer)

-

Gets peer by peer id

+ +

SendToAll(byte[], int, int, byte, DeliveryMethod, LiteNetPeer)

+

Send data to all connected peers

Declaration
-
public bool TryGetPeerById(int id, out NetPeer peer)
+
public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer)
Parameters
@@ -3348,32 +1486,40 @@
Parameters
+ + + + + - - + - - - + + - -
byte[]data

Data

+
intid

id of peer

+
start

Start of data

NetPeerpeer

resulting peer

+
intlength

Length of data

-
Returns
- - - - + + + + + + + + - - - - + + @@ -3382,6 +1528,9 @@

Implements

+ @@ -3396,7 +1545,7 @@

Implements

diff --git a/docs/api/LiteNetLib.NetPacketReader.html b/docs/api/LiteNetLib.NetPacketReader.html index 94c97ef5..df51c9cb 100644 --- a/docs/api/LiteNetLib.NetPacketReader.html +++ b/docs/api/LiteNetLib.NetPacketReader.html @@ -1,15 +1,14 @@ - + - Class NetPacketReader - + Class NetPacketReader - + + @@ -71,13 +70,12 @@ -

Class NetPacketReader -

+

Class NetPacketReader

Inheritance
- +
object
NetPacketReader
@@ -123,17 +121,74 @@
Inherited Members
NetDataReader.SetSource(byte[], int, int) + + + + + + + + + + + + + + + + + + + @@ -167,6 +222,12 @@
Inherited Members
+ + @@ -203,6 +264,12 @@
Inherited Members
+ + @@ -210,14 +277,20 @@
Inherited Members
NetDataReader.GetRemainingBytesSegment() + + @@ -230,6 +303,15 @@
Inherited Members
+ + + @@ -272,6 +354,18 @@
Inherited Members
+ + + + @@ -317,6 +411,9 @@
Inherited Members
+ @@ -350,7 +447,7 @@
Declaration
diff --git a/docs/api/LiteNetLib.NetPeer.html b/docs/api/LiteNetLib.NetPeer.html index cb4a8588..c8234ae8 100644 --- a/docs/api/LiteNetLib.NetPeer.html +++ b/docs/api/LiteNetLib.NetPeer.html @@ -1,16 +1,15 @@ - + - Class NetPeer - + Class NetPeer - + + @@ -71,525 +70,188 @@ -

Class NetPeer -

-

Network peer. Main purpose is sending messages to specific peer.

+

Class NetPeer

+

Improved LiteNetPeer with full multi-channel support

Inheritance
- -
NetPeer
+
object
+ + + +
NetPeer
+
+
+
Inherited Members
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Namespace: LiteNetLib
Assembly: LiteNetLib.dll
Syntax
-
public class NetPeer
-
-

Fields -

- - -

Id

-

Peer id can be used as key in your dictionary of peers

-
-
-
Declaration
-
-
public readonly int Id
-
-
Field Value
-
TypeDescriptionbytechannelNumber

Number of channel (from 0 to channelsCount - 1)

+
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

+
bool

True if peer with id exist, otherwise false

+
LiteNetPeerexcludePeer

Excluded peer

- - - - - - - - - - - - -
TypeDescription
int
- - -

NetManager

-

Peer parent NetManager

-
-
-
Declaration
-
-
public readonly NetManager NetManager
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
NetManager
- - -

Statistics

-

Statistics of peer connection

-
-
-
Declaration
-
-
public readonly NetStatistics Statistics
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
NetStatistics
- - -

Tag

-

Application defined object containing data about the connection

-
-
-
Declaration
-
-
public object Tag
-
-
Field Value
- - - - - - - - - - - - - -
TypeDescription
object
-

Properties -

- - - -

ConnectionState

-

Current connection state

-
-
-
Declaration
-
-
public ConnectionState ConnectionState { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
ConnectionState
- - - -

EndPoint

-

Peer ip address and port

-
-
-
Declaration
-
-
public IPEndPoint EndPoint { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
IPEndPoint
- - - -

Mtu

-

Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation )

-
-
-
Declaration
-
-
public int Mtu { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

Ping

-

Current one-way ping (RTT/2) in milliseconds

-
-
-
Declaration
-
-
public int Ping { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

RemoteId

-

Id assigned from server

-
-
-
Declaration
-
-
public int RemoteId { get; }
+
public class NetPeer : LiteNetPeer
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

RemoteTimeDelta

-

Delta with remote time in ticks (not accurate) -positive - remote time > our time

-
-
-
Declaration
-
-
public long RemoteTimeDelta { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
long
- - - -

RemoteUtcTime

-

Remote UTC time (not accurate)

-
-
-
Declaration
-
-
public DateTime RemoteUtcTime { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
DateTime
- - - -

RoundTripTime

-

Round trip time in milliseconds

-
-
-
Declaration
-
-
public int RoundTripTime { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
- - - -

TimeSinceLastPacket

-

Time since last packet received (including internal library packets)

-
-
-
Declaration
-
-
public int TimeSinceLastPacket { get; }
-
-
Property Value
- - - - - - - - - - - - - -
TypeDescription
int
-

Methods -

- - - -

CreatePacketFromPool(DeliveryMethod, byte)

-

Create temporary packet (maximum size MTU - headerSize) to send later without additional copies

-
-
-
Declaration
-
-
public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
DeliveryMethoddeliveryMethod

Delivery method (reliable, unreliable, etc.)

-
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

-
-
Returns
- - - - - - - - - - - - - -
TypeDescription
PooledPacket

PooledPacket that you can use to write data starting from UserDataOffset

-
- - - -

Disconnect()

-
-
-
Declaration
-
-
public void Disconnect()
-
- - - -

Disconnect(NetDataWriter)

-
-
-
Declaration
-
-
public void Disconnect(NetDataWriter writer)
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
NetDataWriterwriter
- - - -

Disconnect(byte[], int, int)

-
-
-
Declaration
-
-
public void Disconnect(byte[] data, int start, int count)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data
intstart
intcount
+

Properties +

- -

Disconnect(byte[])

+ +

ChannelsCount

Declaration
-
public void Disconnect(byte[] data)
+
protected override int ChannelsCount { get; }
-
Parameters
+
Property Value
- - - +
TypeName Description
byte[]dataint
+
Overrides
+ +

Methods +

- -

GetMaxSinglePacketSize(DeliveryMethod)

-

Gets maximum size of packet that will be not fragmented.

+ +

CreatePacketFromPool(DeliveryMethod, byte)

+

Create temporary packet (maximum size MTU - headerSize) to send later without additional copies

Declaration
-
public int GetMaxSinglePacketSize(DeliveryMethod options)
+
public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber)
Parameters
@@ -603,8 +265,14 @@
Parameters
- - + + + + + + @@ -619,8 +287,8 @@
Returns
- - + @@ -628,13 +296,13 @@
Returns
-

GetPacketsCountInReliableQueue(byte, bool)

-

Returns packets count in queue for reliable channel

+

GetPacketsCountInReliableQueue(bool)

+

Returns packets count in queue for reliable channel 0

Declaration
-
public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered)
+
public int GetPacketsCountInReliableQueue(bool ordered)
Parameters
DeliveryMethodoptions

Type of packet that you want send

+
deliveryMethod

Delivery method (reliable, unreliable, etc.)

+
bytechannelNumber

Number of channel (from 0 to channelsCount - 1)

int

size in bytes

+
PooledPacket

PooledPacket that you can use to write data starting from UserDataOffset

@@ -646,12 +314,6 @@
Parameters
- - - - - @@ -678,14 +340,14 @@
Returns
bytechannelNumber

number of channel 0-63

-
bool ordered
- -

Send(NetDataWriter, DeliveryMethod)

-

Send data to peer (channel - 0)

+ +

GetPacketsCountInReliableQueue(byte, bool)

+

Returns packets count in queue for reliable channel

Declaration
-
public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod)
+
public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered)
Parameters
@@ -698,33 +360,31 @@
Parameters
- - - + + - - - + +
NetDataWriterdataWriter

DataWriter with data

+
bytechannelNumber

number of channel 0-63

DeliveryMethoddeliveryMethod

Send options (reliable, unreliable, etc.)

+
boolordered

type of channel ReliableOrdered or ReliableUnordered

-
Exceptions
+
Returns
- + - - + @@ -790,59 +450,6 @@
Exceptions
TypeConditionDescription
TooBigPacketException

If size exceeds maximum limit:

-MTU - headerSize bytes for Unreliable

-Fragment count exceeded ushort.MaxValue

+
int

packets count in channel queue

- -

Send(byte[], DeliveryMethod)

-

Send data to peer (channel - 0)

-
-
-
Declaration
-
-
public void Send(byte[] data, DeliveryMethod deliveryMethod)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data

-
DeliveryMethoddeliveryMethod

Send options (reliable, unreliable, etc.)

-
-
Exceptions
- - - - - - - - - - - - - -
TypeCondition
TooBigPacketException

If size exceeds maximum limit:

-MTU - headerSize bytes for Unreliable

-Fragment count exceeded ushort.MaxValue

-
- -

Send(byte[], byte, DeliveryMethod)

Send data to peer

@@ -902,71 +509,6 @@
Exceptions
- -

Send(byte[], int, int, DeliveryMethod)

-

Send data to peer (channel - 0)

-
-
-
Declaration
-
-
public void Send(byte[] data, int start, int length, DeliveryMethod options)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
byte[]data

Data

-
intstart

Start of data

-
intlength

Length of data

-
DeliveryMethodoptions

Send options (reliable, unreliable, etc.)

-
-
Exceptions
- - - - - - - - - - - - - -
TypeCondition
TooBigPacketException

If size exceeds maximum limit:

-MTU - headerSize bytes for Unreliable

-Fragment count exceeded ushort.MaxValue

-
- -

Send(byte[], int, int, byte, DeliveryMethod)

Send data to peer

@@ -1038,59 +580,6 @@
Exceptions
- -

Send(ReadOnlySpan<byte>, DeliveryMethod)

-

Send data to peer (channel - 0)

-
-
-
Declaration
-
-
public void Send(ReadOnlySpan<byte> data, DeliveryMethod deliveryMethod)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
ReadOnlySpan<byte>data

Data

-
DeliveryMethoddeliveryMethod

Send options (reliable, unreliable, etc.)

-
-
Exceptions
- - - - - - - - - - - - - -
TypeCondition
TooBigPacketException

If size exceeds maximum limit:

-MTU - headerSize bytes for Unreliable

-Fragment count exceeded ushort.MaxValue

-
- -

Send(ReadOnlySpan<byte>, byte, DeliveryMethod)

Send data to peer

@@ -1150,41 +639,6 @@
Exceptions
- -

SendPooledPacket(PooledPacket, int)

-

Sends pooled packet without data copy

-
-
-
Declaration
-
-
public void SendPooledPacket(PooledPacket packet, int userDataSize)
-
-
Parameters
- - - - - - - - - - - - - - - - - - - - -
TypeNameDescription
PooledPacketpacket

packet to send

-
intuserDataSize

size of user data you want to send

-
- -

SendWithDeliveryEvent(NetDataWriter, byte, DeliveryMethod, object)

Send data to peer with delivery event called

@@ -1223,7 +677,7 @@
Parameters
- object + object userData

User data that will be received in DeliveryEvent

@@ -1286,7 +740,7 @@
Parameters
- object + object userData

User data that will be received in DeliveryEvent

@@ -1361,7 +815,7 @@
Parameters
- object + object userData

User data that will be received in DeliveryEvent

@@ -1424,7 +878,7 @@
Parameters
- object + object userData

User data that will be received in DeliveryEvent

@@ -1448,6 +902,18 @@
Exceptions
+ + +

UpdateChannels()

+
+
+
Declaration
+
+
protected override void UpdateChannels()
+
+
Overrides
+ +
@@ -1458,7 +924,7 @@
Exceptions
diff --git a/docs/api/LiteNetLib.NetStatistics.html b/docs/api/LiteNetLib.NetStatistics.html index 467a448d..19c6e66b 100644 --- a/docs/api/LiteNetLib.NetStatistics.html +++ b/docs/api/LiteNetLib.NetStatistics.html @@ -1,16 +1,15 @@ - + - Class NetStatistics - + Class NetStatistics - + + @@ -71,13 +70,13 @@ -

Class NetStatistics -

-
+

Class NetStatistics

+

Thread-safe counter for network statistics including sent/received packets, bytes, and packet loss.

+
Inheritance
- +
object
NetStatistics
Namespace: LiteNetLib
@@ -92,7 +91,8 @@

Properties

BytesReceived

-
+

Total number of bytes received.

+
Declaration
@@ -117,7 +117,8 @@
Property Value

BytesSent

-
+

Total number of bytes sent.

+
Declaration
@@ -142,7 +143,8 @@
Property Value

PacketLoss

-
+

Total number of packets lost during transmission.

+
Declaration
@@ -167,7 +169,9 @@
Property Value

PacketLossPercent

-
+

Percentage of sent packets that were lost. +Calculated as (PacketLoss * 100) / PacketsSent.

+
Declaration
@@ -192,7 +196,8 @@
Property Value

PacketsReceived

-
+

Total number of packets received.

+
Declaration
@@ -217,7 +222,8 @@
Property Value

PacketsSent

-
+

Total number of packets sent.

+
Declaration
@@ -244,7 +250,8 @@

Methods

AddBytesReceived(long)

-
+

Adds a specific amount to the total bytes received.

+
Declaration
@@ -263,7 +270,8 @@
Parameters
long bytesReceived - +

Number of bytes to add.

+ @@ -271,7 +279,8 @@
Parameters

AddBytesSent(long)

-
+

Adds a specific amount to the total bytes sent.

+
Declaration
@@ -290,7 +299,8 @@
Parameters
long bytesSent - +

Number of bytes to add.

+ @@ -298,7 +308,8 @@
Parameters

AddPacketLoss(long)

-
+

Adds a specific amount to the total packet loss count.

+
Declaration
@@ -317,7 +328,8 @@
Parameters
long packetLoss - +

Number of lost packets to add.

+ @@ -325,7 +337,8 @@
Parameters

IncrementPacketLoss()

-
+

Increments the count of lost packets by one.

+
Declaration
@@ -335,7 +348,8 @@
Declaration

IncrementPacketsReceived()

-
+

Increments the count of received packets by one.

+
Declaration
@@ -345,7 +359,8 @@
Declaration

IncrementPacketsSent()

-
+

Increments the count of sent packets by one.

+
Declaration
@@ -355,7 +370,8 @@
Declaration

Reset()

-
+

Resets all statistical counters to zero.

+
Declaration
@@ -365,7 +381,8 @@
Declaration

ToString()

-
+

Returns a string representation of the current network statistics.

+
Declaration
@@ -382,12 +399,13 @@
Returns
string - +

A formatted string containing bytes received/sent, packets received/sent, and loss information.

+
Overrides
- +
object.ToString()
@@ -399,7 +417,7 @@
Overrides
diff --git a/docs/api/LiteNetLib.NetUtils.html b/docs/api/LiteNetLib.NetUtils.html index a982d647..18dba861 100644 --- a/docs/api/LiteNetLib.NetUtils.html +++ b/docs/api/LiteNetLib.NetUtils.html @@ -1,16 +1,15 @@ - + - Class NetUtils - + Class NetUtils - + + @@ -71,14 +70,13 @@ -

Class NetUtils -

+

Class NetUtils

Some specific network utilities

Inheritance
- +
object
NetUtils
Namespace: LiteNetLib
@@ -218,7 +216,8 @@
Parameters

MakeEndPoint(string, int)

-
+

Creates an IPEndPoint from a host string and a port.

+
Declaration
@@ -237,12 +236,14 @@
Parameters
string hostStr - +

The host name or IP address string to resolve.

+ int port - +

The port number for the endpoint.

+ @@ -257,19 +258,21 @@
Returns
IPEndPoint - +

A new IPEndPoint instance.

+ -

ResolveAddress(string, AddressFamily)

-
+

ResolveAddress(string)

+

Resolves a host string into an IPAddress.

+
Declaration
-
public static IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily)
+
public static IPAddress ResolveAddress(string hostStr)
Parameters
@@ -284,12 +287,8 @@
Parameters
- - - - - - +
string hostStr
AddressFamilyaddressFamily

The host name or IP address string (e.g., "127.0.0.1", "localhost", or "google.com").

+
@@ -304,19 +303,41 @@
Returns
IPAddress - +

The resolved IPAddress.

+ + + + +
Remarks
+

This method handles "localhost" specifically, attempts to parse the string as a direct IP, +and falls back to DNS resolution. It prioritizes IPv6 if IPv6Support is enabled.

+
+
Exceptions
+ + + + + + + + + + +
TypeCondition
ArgumentException

Thrown when the address cannot be resolved or is invalid.

+
-

ResolveAddress(string)

-
+

ResolveAddress(string, AddressFamily)

+

Resolves a host string using DNS for a specific AddressFamily.

+
Declaration
-
public static IPAddress ResolveAddress(string hostStr)
+
public static IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily)
Parameters
@@ -331,7 +352,14 @@
Parameters
- + + + + + +
string hostStr

The host name to resolve via DNS.

+
AddressFamilyaddressFamily

The preferred address family (e.g., InterNetwork or InterNetworkV6).

+
@@ -346,7 +374,8 @@
Returns
IPAddress - +

The first IPAddress matching the family, or null if no match is found.

+ @@ -361,7 +390,7 @@
Returns
diff --git a/docs/api/LiteNetLib.PooledPacket.html b/docs/api/LiteNetLib.PooledPacket.html index abe69e6e..21bc7f4b 100644 --- a/docs/api/LiteNetLib.PooledPacket.html +++ b/docs/api/LiteNetLib.PooledPacket.html @@ -1,15 +1,14 @@ - + - Struct PooledPacket - + Struct PooledPacket - + + @@ -71,8 +70,7 @@ -

Struct PooledPacket -

+

Struct PooledPacket

Namespace: LiteNetLib
@@ -172,7 +170,7 @@
Property Value
diff --git a/docs/api/LiteNetLib.TooBigPacketException.html b/docs/api/LiteNetLib.TooBigPacketException.html index e693f28b..78a1a988 100644 --- a/docs/api/LiteNetLib.TooBigPacketException.html +++ b/docs/api/LiteNetLib.TooBigPacketException.html @@ -1,15 +1,14 @@ - + - Class TooBigPacketException - + Class TooBigPacketException - + + @@ -71,13 +70,12 @@ -

Class TooBigPacketException -

+

Class TooBigPacketException

Inheritance
- +
object
@@ -183,7 +181,7 @@

Implements

diff --git a/docs/api/LiteNetLib.UnconnectedMessageType.html b/docs/api/LiteNetLib.UnconnectedMessageType.html index f1d4a9c8..75c7be41 100644 --- a/docs/api/LiteNetLib.UnconnectedMessageType.html +++ b/docs/api/LiteNetLib.UnconnectedMessageType.html @@ -1,16 +1,15 @@ - + - Enum UnconnectedMessageType - + Enum UnconnectedMessageType - + + @@ -72,8 +71,7 @@ -

Enum UnconnectedMessageType -

+

Enum UnconnectedMessageType

Type of message that you receive in OnNetworkReceiveUnconnected event

@@ -92,7 +90,7 @@

Fields Description - + BasicMessage @@ -102,7 +100,7 @@

Fields - +

@@ -113,7 +111,7 @@

Fields

diff --git a/docs/api/LiteNetLib.Utils.CRC32C.html b/docs/api/LiteNetLib.Utils.CRC32C.html index bf01f7aa..918c04d1 100644 --- a/docs/api/LiteNetLib.Utils.CRC32C.html +++ b/docs/api/LiteNetLib.Utils.CRC32C.html @@ -1,15 +1,14 @@ - + - Class CRC32C - + Class CRC32C - + + @@ -71,13 +70,12 @@ -

Class CRC32C -

+

Class CRC32C

Inheritance
- +
object
CRC32C
Namespace: LiteNetLib.Utils
@@ -182,7 +180,7 @@
Returns
diff --git a/docs/api/LiteNetLib.Utils.FastBitConverter.html b/docs/api/LiteNetLib.Utils.FastBitConverter.html index 13a8c291..ba3f4c92 100644 --- a/docs/api/LiteNetLib.Utils.FastBitConverter.html +++ b/docs/api/LiteNetLib.Utils.FastBitConverter.html @@ -1,15 +1,14 @@ - + - Class FastBitConverter - + Class FastBitConverter - + + @@ -71,13 +70,12 @@ -

Class FastBitConverter -

+

Class FastBitConverter

Inheritance
- +
object
FastBitConverter
Namespace: LiteNetLib.Utils
@@ -92,7 +90,8 @@

Methods

GetBytes<T>(byte[], int, T)

-
+

Converts a value of type T into a byte array starting at the specified index.

+
Declaration
@@ -111,17 +110,20 @@
Parameters
byte[] bytes - +

The destination byte array.

+ int startIndex - +

The zero-based index in bytes at which to begin writing.

+ T value - +

The value to be converted and written.

+ @@ -136,7 +138,24 @@
Type Parameters
T - +

The type of the value to convert. Must be an unmanaged/blittable type.

+ + + + +
Exceptions
+ + + + + + + + + + +
TypeCondition
IndexOutOfRangeException

Thrown when the bytes array is too small to contain the value at the given startIndex.

+
@@ -151,7 +170,7 @@
Type Parameters
diff --git a/docs/api/LiteNetLib.Utils.INetSerializable.html b/docs/api/LiteNetLib.Utils.INetSerializable.html index b01448e3..f536537a 100644 --- a/docs/api/LiteNetLib.Utils.INetSerializable.html +++ b/docs/api/LiteNetLib.Utils.INetSerializable.html @@ -1,16 +1,15 @@ - + - Interface INetSerializable - + Interface INetSerializable - + + @@ -71,9 +70,9 @@ -

Interface INetSerializable -

-
+

Interface INetSerializable

+

Interface for implementing custom data serialization for network transmission.

+
Namespace: LiteNetLib.Utils
Assembly: LiteNetLib.dll
@@ -81,13 +80,17 @@
Syntax
public interface INetSerializable
+
Remarks
+

This is the most efficient way to send complex objects as it avoids reflection.

+

Methods

Deserialize(NetDataReader)

-
+

Reads the object data from the provided NetDataReader.

+
Declaration
@@ -106,7 +109,8 @@
Parameters
NetDataReader reader - +

The reader to extract data from.

+ @@ -114,7 +118,8 @@
Parameters

Serialize(NetDataWriter)

-
+

Writes the object data into the provided NetDataWriter.

+
Declaration
@@ -133,7 +138,8 @@
Parameters
NetDataWriter writer - +

The writer to pack data into.

+ @@ -148,7 +154,7 @@
Parameters
diff --git a/docs/api/LiteNetLib.Utils.InvalidTypeException.html b/docs/api/LiteNetLib.Utils.InvalidTypeException.html index e7b08274..b5c7a616 100644 --- a/docs/api/LiteNetLib.Utils.InvalidTypeException.html +++ b/docs/api/LiteNetLib.Utils.InvalidTypeException.html @@ -1,15 +1,14 @@ - + - Class InvalidTypeException - + Class InvalidTypeException - + + @@ -71,13 +70,12 @@ -

Class InvalidTypeException -

+

Class InvalidTypeException

Inheritance
- +
object
@@ -182,7 +180,7 @@

Implements

diff --git a/docs/api/LiteNetLib.Utils.NetDataReader.html b/docs/api/LiteNetLib.Utils.NetDataReader.html index 57cf769e..4b210e38 100644 --- a/docs/api/LiteNetLib.Utils.NetDataReader.html +++ b/docs/api/LiteNetLib.Utils.NetDataReader.html @@ -1,15 +1,14 @@ - + - Class NetDataReader - + Class NetDataReader - + + @@ -71,13 +70,12 @@ -

Class NetDataReader -

+

Class NetDataReader

Inheritance
- +
object
NetDataReader
@@ -128,6 +126,33 @@
Parameters
+ +

NetDataReader(byte[])

+
+
+
Declaration
+
+
public NetDataReader(byte[] source)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
byte[]source
+ +

NetDataReader(byte[], int, int)

@@ -163,43 +188,40 @@
Parameters
+

Fields +

- -

NetDataReader(byte[])

+

_data

Declaration
-
public NetDataReader(byte[] source)
+
protected byte[] _data
-
Parameters
+
Field Value
- -
TypeName Description
byte[]source
-

Fields -

-

_data

+

_dataSize

Declaration
-
protected byte[] _data
+
protected int _dataSize
Field Value
@@ -211,19 +233,19 @@
Field Value
- +
byte[]int
-

_dataSize

+

_offset

Declaration
-
protected int _dataSize
+
protected int _offset
Field Value
@@ -270,7 +292,8 @@

Properties

AvailableBytes

-
+

Gets the number of bytes remaining to be read.

+
Declaration
@@ -295,7 +318,8 @@
Property Value

EndOfData

-
+

Gets a value indicating whether the Position has reached the end of the data.

+
Declaration
@@ -320,7 +344,8 @@
Property Value

IsNull

-
+

Gets a value indicating whether the internal data buffer is null.

+
Declaration
@@ -345,7 +370,8 @@
Property Value

Position

-
+

Gets the current read position within the buffer.

+
Declaration
@@ -370,7 +396,8 @@
Property Value

RawData

-
+

Gets the internal byte array containing the raw network data.

+
Declaration
@@ -395,7 +422,8 @@
Property Value

RawDataSize

-
+

Gets the total size of the RawData buffer.

+
Declaration
@@ -420,7 +448,8 @@
Property Value

UserDataOffset

-
+

Gets the starting offset of the user payload within the RawData.

+
Declaration
@@ -445,7 +474,8 @@
Property Value

UserDataSize

-
+

Gets the size of the user payload excluding the initial UserDataOffset.

+
Declaration
@@ -472,7 +502,8 @@

Methods

Clear()

-
+

Clears the reader state and releases the reference to the internal buffer.

+
Declaration
@@ -481,39 +512,55 @@
Declaration
-

Get<T>()

-
+

Get(out bool)

+

Reads a bool and assigns it to result.

+
Declaration
-
public T Get<T>() where T : struct, INetSerializable
+
public void Get(out bool result)
-
Returns
+
Parameters
+ - + +
TypeName Description
Tboolresult
-
Type Parameters
+ + + +

Get(out byte)

+

Reads a byte and assigns it to result.

+
+
+
Declaration
+
+
public void Get(out byte result)
+
+
Parameters
+ - + + @@ -521,12 +568,13 @@
Type Parameters
-

Get<T>(Func<T>)

-
+

Get(out char)

+

Reads a char and assigns it to result.

+
Declaration
-
public T Get<T>(Func<T> constructor) where T : class, INetSerializable
+
public void Get(out char result)
Parameters
Type Name Description
Tbyteresult
@@ -539,51 +587,78 @@
Parameters
- - + +
Func<T>constructorcharresult
-
Returns
+ + + +

Get(out double)

+

Reads a double and assigns it to result.

+
+
+
Declaration
+
+
public void Get(out double result)
+
+
Parameters
+ - + +
TypeName Description
Tdoubleresult
-
Type Parameters
+ + + +

Get(out Guid)

+

Reads a Guid and assigns it to result.

+
+
+
Declaration
+
+
public void Get(out Guid result)
+
+
Parameters
+ - + +
Type Name Description
TGuidresult
- -

GetArray<T>(ushort)

-
+ +

Get(out short)

+

Reads a short and assigns it to result.

+
Declaration
-
public T[] GetArray<T>(ushort size)
+
public void Get(out short result)
Parameters
@@ -596,126 +671,163 @@
Parameters
- - + +
ushortsizeshortresult
-
Returns
+ + + +

Get(out int)

+

Reads an int and assigns it to result.

+
+
+
Declaration
+
+
public void Get(out int result)
+
+
Parameters
+ - + +
TypeName Description
T[]intresult
-
Type Parameters
+ + + +

Get(out long)

+

Reads a long and assigns it to result.

+
+
+
Declaration
+
+
public void Get(out long result)
+
+
Parameters
+ - + +
Type Name Description
Tlongresult
- -

GetBool()

-
+ +

Get(out IPEndPoint)

+

Deserializes an IPEndPoint and assigns it to the result parameter.

+
Declaration
-
public bool GetBool()
+
public void Get(out IPEndPoint result)
-
Returns
+
Parameters
+ - - + + +
TypeName Description
boolIPEndPointresult

The deserialized IPEndPoint output.

+
- -

GetBoolArray()

-
+ +

Get(out sbyte)

+

Reads an sbyte and assigns it to result.

+
Declaration
-
public bool[] GetBoolArray()
+
public void Get(out sbyte result)
-
Returns
+
Parameters
+ - + +
TypeName Description
bool[]sbyteresult
- -

GetByte()

-
+ +

Get(out float)

+

Reads a float and assigns it to result.

+
Declaration
-
public byte GetByte()
+
public void Get(out float result)
-
Returns
+
Parameters
+ - + +
TypeName Description
bytefloatresult
- -

GetBytes(byte[], int, int)

-
+ +

Get(out string)

+

Reads a string and assigns it to result.

+
Declaration
-
public void GetBytes(byte[] destination, int start, int count)
+
public void Get(out string result)
Parameters
@@ -728,31 +840,22 @@
Parameters
- - - - - - - - - - - - + +
byte[]destination
intstart
intcountstringresult
- -

GetBytes(byte[], int)

-
+ +

Get(out string, int)

+

Reads a string with a length limit and assigns it to result.

+
Declaration
-
public void GetBytes(byte[] destination, int count)
+
public void Get(out string result, int maxLength)
Parameters
@@ -765,26 +868,27 @@
Parameters
- - + + - +
byte[]destinationstringresult
intcountmaxLength
- -

GetBytesSegment(int)

-
+ +

Get(out ushort)

+

Reads a ushort and assigns it to result.

+
Declaration
-
public ArraySegment<byte> GetBytesSegment(int count)
+
public void Get(out ushort result)
Parameters
@@ -797,36 +901,952 @@
Parameters
- - + +
intcountushortresult
-
Returns
+ + + +

Get(out uint)

+

Reads a uint and assigns it to result.

+
+
+
Declaration
+
+
public void Get(out uint result)
+
+
Parameters
+ - + + + + + +
TypeName Description
ArraySegment<byte>uintresult
+ + + +

Get(out ulong)

+

Reads a ulong and assigns it to result.

+
+
+
Declaration
+
+
public void Get(out ulong result)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
ulongresult
+ + + +

GetArray<T>()

+

Reads an array of objects implementing INetSerializable.

+
+
+
Declaration
+
+
public T[] GetArray<T>() where T : INetSerializable, new()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
T[]

A new array of type T.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

A type with a parameterless constructor implementing INetSerializable.

+
+ + + +

GetArray<T>(Func<T>)

+

Reads an array of objects implementing INetSerializable using a specific constructor.

+
+
+
Declaration
+
+
public T[] GetArray<T>(Func<T> constructor) where T : class, INetSerializable
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
Func<T>constructor

The factory delegate used to create instances.

+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
T[]

A new array of type T.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

A class type implementing INetSerializable.

+
+ + + +

GetArray<T>(ushort)

+

Reads an array of values by performing a direct memory copy.

+
+
+
Declaration
+
+
public T[] GetArray<T>(ushort size)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
ushortsize

The size of a single element in bytes.

+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
T[]

A new array of type T.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

The element type.

+
+ + + +

GetBool()

+

Reads a bool value from the current position.

+
+
+
Declaration
+
+
public bool GetBool()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
bool

true if the byte is 1; otherwise, false.

+
+ + + +

GetBoolArray()

+

Reads an array of bool values.

+
+
+
Declaration
+
+
public bool[] GetBoolArray()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
bool[]
+ + + +

GetByte()

+

Reads the next byte from the buffer.

+
+
+
Declaration
+
+
public byte GetByte()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
byte
+ + + +

GetBytes(byte[], int)

+

Copies a specified number of bytes into a destination array starting at index 0.

+
+
+
Declaration
+
+
public void GetBytes(byte[] destination, int count)
+
+
Parameters
+ + + + + + + + + + + + + + + + + + + + +
TypeNameDescription
byte[]destination

The array to copy data into.

+
intcount

The number of bytes to read.

+
+ + + +

GetBytes(byte[], int, int)

+

Copies a specified number of bytes into a destination array at a specific offset.

+
+
+
Declaration
+
+
public void GetBytes(byte[] destination, int start, int count)
+
+
Parameters
+ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeNameDescription
byte[]destination

The array to copy data into.

+
intstart

The starting index in the destination array.

+
intcount

The number of bytes to read.

+
+ + + +

GetBytesSegment(int)

+

Gets an ArraySegment<T> of bytes from the current position.

+
+
+
Declaration
+
+
public ArraySegment<byte> GetBytesSegment(int count)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
intcount

The number of bytes to include in the segment.

+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
ArraySegment<byte>

An ArraySegment<T> wrapping the internal buffer.

+
+ + + +

GetBytesWithLength()

+

Reads a byte array prefixed with its ushort length.

+
+
+
Declaration
+
+
public byte[] GetBytesWithLength()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
byte[]

A new byte array.

+
+ + + +

GetChar()

+

Reads a char value as a 2-byte ushort.

+
+
+
Declaration
+
+
public char GetChar()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
char
+ + + +

GetDouble()

+

Reads a double value using unmanaged memory access.

+
+
+
Declaration
+
+
public double GetDouble()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
double
+ + + +

GetDoubleArray()

+

Reads an array of double values.

+
+
+
Declaration
+
+
public double[] GetDoubleArray()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
double[]
+ + + +

GetEnum<T>()

+

Reads an enum value of type T from the internal data buffer at the current position.
+Advances the position by the size of T.

+
+
+
Declaration
+
+
public T GetEnum<T>() where T : unmanaged, Enum
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
T

The enum value read from the buffer.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

An unmanaged enum type to read.

+
+ + + +

GetFloat()

+

Reads a float value using unmanaged memory access.

+
+
+
Declaration
+
+
public float GetFloat()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
float
+ + + +

GetFloatArray()

+

Reads an array of float values.

+
+
+
Declaration
+
+
public float[] GetFloatArray()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
float[]
+ + + +

GetGuid()

+

Reads a 16-byte Guid.

+
+
+
Declaration
+
+
public Guid GetGuid()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
Guid

The deserialized Guid.

+
+ + + +

GetIPEndPoint()

+

Reads an IPEndPoint from the current Position.

+
+
+
Declaration
+
+
public IPEndPoint GetIPEndPoint()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
IPEndPoint

The deserialized IPEndPoint.

+
+
Remarks
+

Reads a byte to determine the AddressFamily (0 for IPv4, 1 for IPv6), +followed by the address bytes and a 2-byte ushort port.

+
+ + + +

GetInt()

+

Reads an int value using unmanaged memory access.

+
+
+
Declaration
+
+
public int GetInt()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
int
+ + + +

GetIntArray()

+

Reads an array of int values.

+
+
+
Declaration
+
+
public int[] GetIntArray()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
int[]
+ + + +

GetLargeString()

+

Reads a string prefixed with a 4-byte int length header.

+
+
+
Declaration
+
+
public string GetLargeString()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
string

The deserialized string.

+
+ + + +

GetLong()

+

Reads a long value using unmanaged memory access.

+
+
+
Declaration
+
+
public long GetLong()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
long
+ + + +

GetLongArray()

+

Reads an array of long values.

+
+
+
Declaration
+
+
public long[] GetLongArray()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
long[]
+ + + +

GetNullableUnmanaged<T>()

+

Reads a nullable value of type T from the internal byte buffer at the current position, +first reading a bool 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 T if the value is present.

+
+
+
Declaration
+
+
public T? GetNullableUnmanaged<T>() where T : unmanaged
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
T?

The nullable value of type T read from the buffer. +Returns null if the presence flag indicates no value.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

An unmanaged value type to read from the buffer.

+
+
Exceptions
+ + + + + + + + + +
TypeCondition
IndexOutOfRangeException
- -

GetBytesWithLength()

-
+ +

GetRemainingBytes()

+

Reads all remaining bytes and returns them as a new array.

+
+
+
Declaration
+
+
public byte[] GetRemainingBytes()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
byte[]

A new byte array containing the remaining data.

+
+
Remarks
+

This method performs a heap allocation and advances the Position to the end of the data.

+
+ + + +

GetRemainingBytesMemory()

+

Returns a ReadOnlyMemory<T> of bytes containing all remaining data.

+
+
+
Declaration
+
+
public ReadOnlyMemory<byte> GetRemainingBytesMemory()
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
ReadOnlyMemory<byte>

A ReadOnlyMemory<T> from the current Position to the end of the buffer.

+
+ + + +

GetRemainingBytesSegment()

+

Gets an ArraySegment<T> containing all remaining bytes.

+
Declaration
-
public byte[] GetBytesWithLength()
+
public ArraySegment<byte> GetRemainingBytesSegment()
Returns
@@ -838,20 +1858,22 @@
Returns
- - + +
byte[]ArraySegment<byte>

An ArraySegment<T> from the current position to the end of the data.

+
- -

GetChar()

-
+ +

GetRemainingBytesSpan()

+

Returns a ReadOnlySpan<T> of bytes containing all remaining data.

+
Declaration
-
public char GetChar()
+
public ReadOnlySpan<byte> GetRemainingBytesSpan()
Returns
@@ -863,20 +1885,22 @@
Returns
- - + +
charReadOnlySpan<byte>

A ReadOnlySpan<T> from the current Position to the end of the buffer.

+
- -

GetDouble()

-
+ +

GetSByte()

+

Reads the next sbyte from the buffer.

+
Declaration
-
public double GetDouble()
+
public sbyte GetSByte()
Returns
@@ -888,20 +1912,21 @@
Returns
- +
doublesbyte
- -

GetDoubleArray()

-
+ +

GetSBytesWithLength()

+

Reads an sbyte array prefixed with its ushort length.

+
Declaration
-
public double[] GetDoubleArray()
+
public sbyte[] GetSBytesWithLength()
Returns
@@ -913,20 +1938,22 @@
Returns
- - + +
double[]sbyte[]

A new sbyte array.

+
- -

GetFloat()

-
+ +

GetShort()

+

Reads a short value using unmanaged memory access.

+
Declaration
-
public float GetFloat()
+
public short GetShort()
Returns
@@ -938,20 +1965,21 @@
Returns
- +
floatshort
- -

GetFloatArray()

-
+ +

GetShortArray()

+

Reads an array of short values.

+
Declaration
-
public float[] GetFloatArray()
+
public short[] GetShortArray()
Returns
@@ -963,20 +1991,21 @@
Returns
- +
float[]short[]
- -

GetInt()

-
+ +

GetString()

+

Reads a string from the current position.

+
Declaration
-
public int GetInt()
+
public string GetString()
Returns
@@ -986,22 +2015,72 @@
Returns
+ + + + + + +
Description
string

The deserialized string.

+
+ + + +

GetString(int)

+

Reads a string with a maximum character limit.

+
+
+
Declaration
+
+
public string GetString(int maxLength)
+
+
Parameters
+ + + + + + + + - + + + + +
TypeNameDescription
intmaxLength

The maximum allowed character count.

+
+
Returns
+ + + + + + + + + + +
TypeDescription
string

The deserialized string, or Empty if the character count exceeds maxLength.

+
+
Remarks
+

Note that maxLength limits the number of characters, not the total size in bytes.

+
- -

GetIntArray()

-
+ +

GetStringArray()

+

Reads an array of string values.

+
Declaration
-
public int[] GetIntArray()
+
public string[] GetStringArray()
Returns
@@ -1013,20 +2092,73 @@
Returns
- - + +
int[]string[]

A new string array.

+
+
Remarks
+

Reads a 2-byte ushort length header followed by each string element.

+
- -

GetLong()

-
+ +

GetStringArray(int)

+

Reads an array of string values with a maximum character limit per element.

+
Declaration
-
public long GetLong()
+
public string[] GetStringArray(int maxStringLength)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
intmaxStringLength

The maximum number of characters allowed per string.

+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
string[]

A new string array.

+
+
Remarks
+

Strings exceeding maxStringLength are returned as Empty.

+
+ + + +

GetUInt()

+

Reads a uint value using unmanaged memory access.

+
+
+
Declaration
+
+
public uint GetUInt()
Returns
@@ -1038,20 +2170,21 @@
Returns
- +
longuint
- -

GetLongArray()

-
+ +

GetUIntArray()

+

Reads an array of uint values.

+
Declaration
-
public long[] GetLongArray()
+
public uint[] GetUIntArray()
Returns
@@ -1063,20 +2196,21 @@
Returns
- +
long[]uint[]
- -

GetNetEndPoint()

-
+ +

GetULong()

+

Reads a ulong value using unmanaged memory access.

+
Declaration
-
public IPEndPoint GetNetEndPoint()
+
public ulong GetULong()
Returns
@@ -1088,20 +2222,21 @@
Returns
- +
IPEndPointulong
- -

GetRemainingBytes()

-
+ +

GetULongArray()

+

Reads an array of ulong values.

+
Declaration
-
public byte[] GetRemainingBytes()
+
public ulong[] GetULongArray()
Returns
@@ -1113,20 +2248,21 @@
Returns
- +
byte[]ulong[]
- -

GetRemainingBytesSegment()

-
+ +

GetUShort()

+

Reads a ushort value using unmanaged memory access.

+
Declaration
-
public ArraySegment<byte> GetRemainingBytesSegment()
+
public ushort GetUShort()
Returns
@@ -1138,20 +2274,21 @@
Returns
- +
ArraySegment<byte>ushort
- -

GetSByte()

-
+ +

GetUShortArray()

+

Reads an array of ushort values.

+
Declaration
-
public sbyte GetSByte()
+
public ushort[] GetUShortArray()
Returns
@@ -1163,20 +2300,21 @@
Returns
- +
sbyteushort[]
- -

GetSBytesWithLength()

-
+ +

GetUnmanagedArray<T>()

+

Reads an array of unmanaged values prefixed by a ushort length.

+
Declaration
-
public sbyte[] GetSBytesWithLength()
+
public T[] GetUnmanagedArray<T>() where T : unmanaged
Returns
@@ -1188,20 +2326,39 @@
Returns
- - + + + + +
sbyte[]T[]

A new array of type T.

+
+
Type Parameters
+ + + + + + + + + + +
NameDescription
T

An unmanaged type.

+
- -

GetShort()

-
+ +

GetUnmanaged<T>()

+

Reads a value of type T from the internal byte buffer at the current position, +advancing the position by the size of T.

+
Declaration
-
public short GetShort()
+
public T GetUnmanaged<T>() where T : unmanaged
Returns
@@ -1213,258 +2370,253 @@
Returns
- - + +
shortT

The value of type T read from the buffer.

+
- - - -

GetShortArray()

-
-
-
Declaration
-
-
public short[] GetShortArray()
-
-
Returns
+
Type Parameters
- + - - + +
TypeName Description
short[]T

An unmanaged value type to read from the buffer.

+
- - - -

GetString()

-
-
-
Declaration
-
-
public string GetString()
-
-
Returns
+
Exceptions
- + - +
TypeDescriptionCondition
stringIndexOutOfRangeException
- -

GetString(int)

-

Note that "maxLength" only limits the number of characters in a string, not its size in bytes.

+ +

Get<T>()

+

Deserializes a struct that implements INetSerializable.

Declaration
-
public string GetString(int maxLength)
+
public T Get<T>() where T : struct, INetSerializable
-
Parameters
+
Returns
- - - - + +
TypeName Description
intmaxLengthT

The deserialized struct.

+
-
Returns
+
Type Parameters
- + - - +
TypeName Description
string

"string.Empty" if value > "maxLength"

+
T

A struct type implementing INetSerializable.

- -

GetStringArray()

-
+ +

Get<T>(Func<T>)

+

Deserializes a class that implements INetSerializable using a provided constructor.

+
Declaration
-
public string[] GetStringArray()
+
public T Get<T>(Func<T> constructor) where T : class, INetSerializable
-
Returns
+
Parameters
+ - - + + +
TypeName Description
string[]Func<T>constructor

The factory delegate used to instantiate the class.

+
- - - -

GetStringArray(int)

-

Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. -Strings that exceed this parameter are returned as empty

-
-
-
Declaration
-
-
public string[] GetStringArray(int maxStringLength)
-
-
Parameters
+
Returns
- - - - + +
TypeName Description
intmaxStringLengthT

A new instance of T.

+
-
Returns
+
Type Parameters
- + - - + +
TypeName Description
string[]T

A class type implementing INetSerializable.

+
- -

GetUInt()

-
+ +

Get<T>(out T)

+

Deserializes a struct that implements INetSerializable.

+
Declaration
-
public uint GetUInt()
+
public void Get<T>(out T result) where T : struct, INetSerializable
-
Returns
+
Parameters
+ - - + + +
TypeName Description
uintTresult

The deserialized struct output.

+
- - - -

GetUIntArray()

-
-
-
Declaration
-
-
public uint[] GetUIntArray()
-
-
Returns
+
Type Parameters
- + - - + +
TypeName Description
uint[]T

A struct type implementing INetSerializable.

+
- -

GetULong()

-
+ +

Get<T>(out T, Func<T>)

+

Deserializes a class that implements INetSerializable using a provided constructor.

+
Declaration
-
public ulong GetULong()
+
public void Get<T>(out T result, Func<T> constructor) where T : class, INetSerializable
-
Returns
+
Parameters
+ - - + + + + + + + + + + +
TypeName Description
ulongTresult

The deserialized class instance output.

+
Func<T>constructor

A factory delegate used to instantiate the class.

+
+
Type Parameters
+ + + + + + + + + + +
NameDescription
T

A class type implementing INetSerializable.

+
- -

GetULongArray()

-
+ +

PeekBool()

+

Reads the bool at the current position without advancing the Position.

+
Declaration
-
public ulong[] GetULongArray()
+
public bool PeekBool()
Returns
@@ -1476,20 +2628,21 @@
Returns
- +
ulong[]bool
- -

GetUShort()

-
+ +

PeekByte()

+

Reads the byte at the current position without advancing the Position.

+
Declaration
-
public ushort GetUShort()
+
public byte PeekByte()
Returns
@@ -1501,20 +2654,21 @@
Returns
- +
ushortbyte
- -

GetUShortArray()

-
+ +

PeekChar()

+

Reads the char at the current position without advancing the Position.

+
Declaration
-
public ushort[] GetUShortArray()
+
public char PeekChar()
Returns
@@ -1526,20 +2680,21 @@
Returns
- +
ushort[]char
- -

PeekBool()

-
+ +

PeekDouble()

+

Reads the double at the current position without advancing the Position.

+
Declaration
-
public bool PeekBool()
+
public double PeekDouble()
Returns
@@ -1551,20 +2706,21 @@
Returns
- +
booldouble
- -

PeekByte()

-
+ +

PeekFloat()

+

Reads the float at the current position without advancing the Position.

+
Declaration
-
public byte PeekByte()
+
public float PeekFloat()
Returns
@@ -1576,20 +2732,21 @@
Returns
- +
bytefloat
- -

PeekChar()

-
+ +

PeekInt()

+

Reads the int at the current position without advancing the Position.

+
Declaration
-
public char PeekChar()
+
public int PeekInt()
Returns
@@ -1601,20 +2758,21 @@
Returns
- +
charint
- -

PeekDouble()

-
+ +

PeekLong()

+

Reads the long at the current position without advancing the Position.

+
Declaration
-
public double PeekDouble()
+
public long PeekLong()
Returns
@@ -1626,20 +2784,21 @@
Returns
- +
doublelong
- -

PeekFloat()

-
+ +

PeekRemainingBytes()

+

Reads all remaining bytes and returns them as a new array without advancing the reader _position.

+
Declaration
-
public float PeekFloat()
+
public byte[] PeekRemainingBytes()
Returns
@@ -1651,20 +2810,25 @@
Returns
- - + +
floatbyte[]

A new byte array containing the remaining data.

+
+
Remarks
+

This method performs a heap allocation by copying the data into a new array.

+
- -

PeekInt()

-
+ +

PeekRemainingBytesMemory()

+

Returns a ReadOnlyMemory<T> of bytes containing all remaining data without advancing the reader _position.

+
Declaration
-
public int PeekInt()
+
public ReadOnlyMemory<byte> PeekRemainingBytesMemory()
Returns
@@ -1676,20 +2840,22 @@
Returns
- - + +
intReadOnlyMemory<byte>

A ReadOnlyMemory<T> from the current Position to the end of the buffer.

+
- -

PeekLong()

-
+ +

PeekRemainingBytesSpan()

+

Returns a ReadOnlySpan<T> of bytes containing all remaining data without advancing the reader _position.

+
Declaration
-
public long PeekLong()
+
public ReadOnlySpan<byte> PeekRemainingBytesSpan()
Returns
@@ -1701,8 +2867,9 @@
Returns
- - + +
longReadOnlySpan<byte>

A ReadOnlySpan<T> from the current Position to the end of the buffer.

+
@@ -1710,7 +2877,8 @@
Returns

PeekSByte()

-
+

Reads the sbyte at the current position without advancing the Position.

+
Declaration
@@ -1735,7 +2903,8 @@
Returns

PeekShort()

-
+

Reads the short at the current position without advancing the Position.

+
Declaration
@@ -1760,7 +2929,8 @@
Returns

PeekString()

-
+

Reads a string without advancing the Position.

+
Declaration
@@ -1785,7 +2955,7 @@
Returns

PeekString(int)

-

Note that "maxLength" only limits the number of characters in a string, not its size in bytes.

+

Reads a string with a character limit without advancing the Position.

Declaration
@@ -1805,7 +2975,8 @@
Parameters
int maxLength - +

Maximum allowed character count.

+ @@ -1824,11 +2995,15 @@
Returns
+
Remarks
+

Strings exceeding maxLength are returned as Empty.

+

PeekUInt()

-
+

Reads the uint at the current position without advancing the Position.

+
Declaration
@@ -1853,7 +3028,8 @@
Returns

PeekULong()

-
+

Reads the ulong at the current position without advancing the Position.

+
Declaration
@@ -1878,7 +3054,8 @@
Returns

PeekUShort()

-
+

Reads the ushort at the current position without advancing the Position.

+
Declaration
@@ -1901,9 +3078,53 @@
Returns
+ +

PeekUnmanaged<T>()

+

Reads an unmanaged value of type T at the current position without advancing the Position.

+
+
+
Declaration
+
+
public T PeekUnmanaged<T>() where T : unmanaged
+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
T

The value read from the buffer.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

An unmanaged value type.

+
+ +

SetPosition(int)

-
+

Sets the current read Position to a specific index.

+
Declaration
@@ -1922,7 +3143,8 @@
Parameters
int position - +

The index to move the Position to.

+ @@ -1930,7 +3152,8 @@
Parameters

SetSource(NetDataWriter)

-
+

Reinitializes the reader using data from a NetDataWriter.

+
Declaration
@@ -1949,19 +3172,21 @@
Parameters
NetDataWriter dataWriter - +

The source NetDataWriter.

+ -

SetSource(byte[], int, int)

-
+

SetSource(byte[])

+

Reinitializes the reader using a byte array.

+
Declaration
-
public void SetSource(byte[] source, int offset, int maxSize)
+
public void SetSource(byte[] source)
Parameters
@@ -1976,29 +3201,21 @@
Parameters
- - - - - - - - - - - +
byte[] source
intoffset
intmaxSize

The source byte array.

+
-

SetSource(byte[])

-
+

SetSource(byte[], int, int)

+

Reinitializes the reader using a segment of a byte array.

+
Declaration
-
public void SetSource(byte[] source)
+
public void SetSource(byte[] source, int offset, int maxSize)
Parameters
@@ -2013,7 +3230,20 @@
Parameters
- + + + + + + + + + + +
byte[] source

The source byte array.

+
intoffset

The starting index for reading.

+
intmaxSize

The total number of bytes available to read from the source.

+
@@ -2021,7 +3251,8 @@
Parameters

SkipBytes(int)

-
+

Advances the Position by the specified count.

+
Declaration
@@ -2040,7 +3271,8 @@
Parameters
int count - +

The number of bytes to skip.

+ @@ -2048,7 +3280,8 @@
Parameters

TryGetBool(out bool)

-
+

Attempts to read a bool without throwing an exception.

+
Declaration
@@ -2067,7 +3300,8 @@
Parameters
bool result - +

The deserialized bool, or false if failed.

+ @@ -2082,7 +3316,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2090,7 +3325,8 @@
Returns

TryGetByte(out byte)

-
+

Attempts to read a byte without throwing an exception.

+
Declaration
@@ -2109,7 +3345,8 @@
Parameters
byte result - +

The deserialized byte, or 0 if failed.

+ @@ -2124,7 +3361,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2132,7 +3370,8 @@
Returns

TryGetBytesWithLength(out byte[])

-
+

Attempts to read a byte array with a length header without throwing an exception.

+
Declaration
@@ -2151,7 +3390,8 @@
Parameters
byte[] result - +

The deserialized byte array, or null if failed.

+ @@ -2166,7 +3406,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2174,7 +3415,8 @@
Returns

TryGetChar(out char)

-
+

Attempts to read a char without throwing an exception.

+
Declaration
@@ -2193,7 +3435,8 @@
Parameters
char result - +

The deserialized char, or '\0' if failed.

+ @@ -2208,7 +3451,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2216,7 +3460,8 @@
Returns

TryGetDouble(out double)

-
+

Attempts to read a double without throwing an exception.

+
Declaration
@@ -2235,7 +3480,8 @@
Parameters
double result - +

The deserialized double, or 0 if failed.

+ @@ -2250,7 +3496,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2258,7 +3505,8 @@
Returns

TryGetFloat(out float)

-
+

Attempts to read a float without throwing an exception.

+
Declaration
@@ -2277,7 +3525,8 @@
Parameters
float result - +

The deserialized float, or 0 if failed.

+ @@ -2292,7 +3541,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2300,7 +3550,8 @@
Returns

TryGetInt(out int)

-
+

Attempts to read an int without throwing an exception.

+
Declaration
@@ -2319,7 +3570,8 @@
Parameters
int result - +

The deserialized int, or 0 if failed.

+ @@ -2334,7 +3586,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2342,7 +3595,8 @@
Returns

TryGetLong(out long)

-
+

Attempts to read a long without throwing an exception.

+
Declaration
@@ -2361,7 +3615,8 @@
Parameters
long result - +

The deserialized long, or 0 if failed.

+ @@ -2376,7 +3631,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2384,7 +3640,8 @@
Returns

TryGetSByte(out sbyte)

-
+

Attempts to read an sbyte without throwing an exception.

+
Declaration
@@ -2403,7 +3660,8 @@
Parameters
sbyte result - +

The deserialized sbyte, or 0 if failed.

+ @@ -2418,7 +3676,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2426,7 +3685,8 @@
Returns

TryGetShort(out short)

-
+

Attempts to read a short without throwing an exception.

+
Declaration
@@ -2445,7 +3705,8 @@
Parameters
short result - +

The deserialized short, or 0 if failed.

+ @@ -2460,7 +3721,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2468,7 +3730,8 @@
Returns

TryGetString(out string)

-
+

Attempts to read a string without throwing an exception.

+
Declaration
@@ -2487,7 +3750,8 @@
Parameters
string result - +

The deserialized string, or null if failed.

+ @@ -2502,7 +3766,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2510,7 +3775,8 @@
Returns

TryGetStringArray(out string[])

-
+

Attempts to read a string array without throwing an exception.

+
Declaration
@@ -2529,7 +3795,8 @@
Parameters
string[] result - +

The deserialized string array, or null if failed.

+ @@ -2544,7 +3811,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2552,7 +3820,8 @@
Returns

TryGetUInt(out uint)

-
+

Attempts to read a uint without throwing an exception.

+
Declaration
@@ -2571,7 +3840,8 @@
Parameters
uint result - +

The deserialized uint, or 0 if failed.

+ @@ -2586,7 +3856,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2594,7 +3865,8 @@
Returns

TryGetULong(out ulong)

-
+

Attempts to read a ulong without throwing an exception.

+
Declaration
@@ -2613,7 +3885,8 @@
Parameters
ulong result - +

The deserialized ulong, or 0 if failed.

+ @@ -2628,7 +3901,8 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ @@ -2636,7 +3910,8 @@
Returns

TryGetUShort(out ushort)

-
+

Attempts to read a ushort without throwing an exception.

+
Declaration
@@ -2655,7 +3930,8 @@
Parameters
ushort result - +

The deserialized ushort, or 0 if failed.

+ @@ -2670,7 +3946,70 @@
Returns
bool - +

true if enough data was available; otherwise, false.

+ + + + + + + +

TryGetUnmanaged<T>(out T)

+

Attempts to read a value of type T from the internal byte buffer at the current position, +advancing the position by the size of T if successful.

+
+
+
Declaration
+
+
public bool TryGetUnmanaged<T>(out T result) where T : unmanaged
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
Tresult

When this method returns, contains the value read from the buffer, or the default value if the read failed.

+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
bool

true if enough data was available to read the value; otherwise, false.

+
+
Type Parameters
+ + + + + + + + + + +
NameDescription
T

An unmanaged value type to read from the buffer.

+
@@ -2685,7 +4024,7 @@
Returns
diff --git a/docs/api/LiteNetLib.Utils.NetDataWriter.html b/docs/api/LiteNetLib.Utils.NetDataWriter.html index ad9fd94a..39a22e04 100644 --- a/docs/api/LiteNetLib.Utils.NetDataWriter.html +++ b/docs/api/LiteNetLib.Utils.NetDataWriter.html @@ -1,15 +1,14 @@ - + - Class NetDataWriter - + Class NetDataWriter - + + @@ -71,13 +70,12 @@ -

Class NetDataWriter -

+

Class NetDataWriter

Inheritance
- +
object
NetDataWriter
Namespace: LiteNetLib.Utils
@@ -101,12 +99,12 @@
Declaration
-

NetDataWriter(bool, int)

+

NetDataWriter(bool)

Declaration
-
public NetDataWriter(bool autoResize, int initialSize)
+
public NetDataWriter(bool autoResize)
Parameters
@@ -123,22 +121,17 @@
Parameters
- - - - -
autoResize
intinitialSize
-

NetDataWriter(bool)

+

NetDataWriter(bool, int)

Declaration
-
public NetDataWriter(bool autoResize)
+
public NetDataWriter(bool autoResize, int initialSize)
Parameters
@@ -155,6 +148,11 @@
Parameters
+ + + + +
autoResize
intinitialSize

Fields @@ -207,16 +205,20 @@

Field Value
+

Properties +

-

StringBufferMaxLength

-
+ +

Capacity

+

Gets the total capacity of the internal byte buffer.

+
Declaration
-
public const int StringBufferMaxLength = 65535
+
public int Capacity { get; }
-
Field Value
+
Property Value
@@ -227,45 +229,21 @@
Field Value
- - - -
int
- - -

uTF8Encoding

-
-
-
Declaration
-
-
public static readonly ThreadLocal<UTF8Encoding> uTF8Encoding
-
-
Field Value
- - - - - - - - - - - +
TypeDescription
ThreadLocal<UTF8Encoding>

The length of the underlying _data array.

+
-

Properties -

- -

Capacity

-
+ +

Data

+

Gets the underlying byte array used by this writer.

+
Declaration
-
public int Capacity { get; }
+
public byte[] Data { get; }
Property Value
@@ -277,20 +255,25 @@
Property Value
- - + +
intbyte[]

The internal _data array.

+
+
Remarks
+

Accessing this directly allows for external manipulation but bypasses bounds checking.

+
- -

Data

-
+ +

Length

+

Gets the current number of bytes written to the buffer.

+
Declaration
-
public byte[] Data { get; }
+
public int Length { get; }
Property Value
@@ -302,22 +285,26 @@
Property Value
- - + +
byte[]int

The current _position.

+
+

Methods +

- -

Length

-
+ +

AsReadOnlySpan()

+

Returns a ReadOnlySpan<T> representing the currently used portion of the internal buffer.

+
Declaration
-
public int Length { get; }
+
public ReadOnlySpan<byte> AsReadOnlySpan()
-
Property Value
+
Returns
@@ -327,18 +314,22 @@
Property Value
- - + +
intReadOnlySpan<byte>

A ReadOnlySpan<T> from index 0 to _position.

+
-

Methods -

+
Remarks
+

Provides a high-performance, zero-allocation view of the data. +The span becomes invalid if the internal buffer is resized or if _position changes.

+

CopyData()

-
+

Creates a byte array containing the current data from the internal buffer.

+
Declaration
@@ -355,15 +346,20 @@
Returns
byte[] - +

A new byte array of length _position.

+ +
Remarks
+

This method performs a heap allocation and copies the data using BlockCopy(Array, int, Array, int, int).

+

EnsureFit(int)

-
+

Ensures the internal buffer can accommodate additionalSize more bytes.

+
Declaration
@@ -382,10 +378,15 @@
Parameters
int additionalSize - +

The number of additional bytes to fit.

+ +
Remarks
+

This checks against the current _position. If the capacity is insufficient, +the buffer grows to either the required size or doubles its current size.

+
@@ -494,9 +495,53 @@
Returns
+ +

FromBytes(Span<byte>)

+

Creates NetDataWriter from the given bytes.

+
+
+
Declaration
+
+
public static NetDataWriter FromBytes(Span<byte> bytes)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
Span<byte>bytes
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
NetDataWriter
+ +

FromString(string)

-
+

Creates a new NetDataWriter and serializes a string into it.

+
Declaration
@@ -515,7 +560,8 @@
Parameters
string value - +

The string to serialize.

+ @@ -530,7 +576,8 @@
Returns
NetDataWriter - +

A new NetDataWriter instance containing the serialized string.

+ @@ -538,7 +585,8 @@
Returns

Put(bool)

-
+

Serializes a bool value as a single byte.

+
Declaration
@@ -557,7 +605,8 @@
Parameters
bool value - +

The bool value to write.

+ @@ -565,7 +614,8 @@
Parameters

Put(byte)

-
+

Serializes a byte value.

+
Declaration
@@ -584,7 +634,37 @@
Parameters
byte value - +

The byte value to write.

+ + + + + + + +

Put(byte[])

+

Serializes an entire byte array.

+
+
+
Declaration
+
+
public void Put(byte[] data)
+
+
Parameters
+ + + + + + + + + + + + +
TypeNameDescription
byte[]data

The source array.

+
@@ -592,7 +672,8 @@
Parameters

Put(byte[], int, int)

-
+

Serializes a segment of a byte array.

+
Declaration
@@ -611,29 +692,33 @@
Parameters
byte[] data - +

The source array.

+ int offset - +

The starting index in the source array.

+ int length - +

The number of bytes to write.

+ -

Put(byte[])

-
+

Put(char)

+

Serializes a char value as a ushort.

+
Declaration
-
public void Put(byte[] data)
+
public void Put(char value)
Parameters
@@ -646,21 +731,23 @@
Parameters
- - - + + +
byte[]datacharvalue

The char value to write.

+
-

Put(char)

-
+

Put(double)

+

Serializes a double value.

+
Declaration
-
public void Put(char value)
+
public void Put(double value)
Parameters
@@ -673,21 +760,23 @@
Parameters
- + - +
chardouble value

The double value to write.

+
-

Put(double)

-
+

Put(Guid)

+

Serializes a Guid value.

+
Declaration
-
public void Put(double value)
+
public void Put(Guid value)
Parameters
@@ -700,9 +789,10 @@
Parameters
- + - +
doubleGuid value

The Guid value to write.

+
@@ -710,7 +800,8 @@
Parameters

Put(short)

-
+

Serializes a short value.

+
Declaration
@@ -729,7 +820,8 @@
Parameters
short value - +

The short value to write.

+ @@ -737,7 +829,8 @@
Parameters

Put(int)

-
+

Serializes an int value.

+
Declaration
@@ -756,7 +849,8 @@
Parameters
int value - +

The int value to write.

+ @@ -764,7 +858,8 @@
Parameters

Put(long)

-
+

Serializes a long value.

+
Declaration
@@ -783,7 +878,8 @@
Parameters
long value - +

The long value to write.

+ @@ -791,7 +887,8 @@
Parameters

Put(IPEndPoint)

-
+

Serializes an IPEndPoint.

+
Declaration
@@ -810,19 +907,40 @@
Parameters
IPEndPoint endPoint - +

The network endpoint to write.

+ + + + +
Remarks
+

Writes a byte (0 for IPv4, 1 for IPv6), followed by the address bytes and a 2-byte ushort port.

+
+
Exceptions
+ + + + + + + + + + +
TypeCondition
ArgumentException

Thrown when the AddressFamily is not InterNetwork or InterNetworkV6.

+
-

Put(sbyte)

-
+

Put(ReadOnlySpan<byte>)

+

Serializes a ReadOnlySpan<T> of bytes to the internal buffer.

+
Declaration
-
public void Put(sbyte value)
+
public void Put(ReadOnlySpan<byte> data)
Parameters
@@ -835,21 +953,23 @@
Parameters
- - - + + +
sbytevalueReadOnlySpan<byte>data

The span of data to write.

+
-

Put(float)

-
+

Put(sbyte)

+

Serializes an sbyte value.

+
Declaration
-
public void Put(float value)
+
public void Put(sbyte value)
Parameters
@@ -862,22 +982,23 @@
Parameters
- + - +
floatsbyte value

The sbyte value to write.

+
-

Put(string, int)

-

Note that "maxLength" only limits the number of characters in a string, not its size in bytes.

+

Put(float)

+

Serializes a float value.

Declaration
-
public void Put(string value, int maxLength)
+
public void Put(float value)
Parameters
@@ -890,26 +1011,23 @@
Parameters
- + - - - - - - +
stringfloat value
intmaxLength

The float value to write.

+
-

Put(string)

-
+

Put(string, int)

+

Serializes a string using a 2-byte ushort length header.

+
Declaration
-
public void Put(string value)
+
public void Put(string value, int maxLength = 0)
Parameters
@@ -924,15 +1042,28 @@
Parameters
- + + + + + +
string value

The string to write to the buffer.

+
intmaxLength

The maximum number of characters to write. If the string is longer, it will be truncated.
+A value of 0 indicates no limit.

+
+
Remarks
+

Note that maxLength limits the number of characters, not the total size in bytes.
+Uses UTF8.

+

Put(ushort)

-
+

Serializes a ushort value.

+
Declaration
@@ -951,7 +1082,8 @@
Parameters
ushort value - +

The ushort value to write.

+ @@ -959,7 +1091,8 @@
Parameters

Put(uint)

-
+

Serializes a uint value.

+
Declaration
@@ -978,7 +1111,8 @@
Parameters
uint value - +

The uint value to write.

+ @@ -986,7 +1120,8 @@
Parameters

Put(ulong)

-
+

Serializes a ulong value.

+
Declaration
@@ -1005,49 +1140,8 @@
Parameters
ulong value - - - - - - - -

Put<T>(T)

-
-
-
Declaration
-
-
public void Put<T>(T obj) where T : INetSerializable
-
-
Parameters
- - - - - - - - - - - - - - - -
TypeNameDescription
Tobj
-
Type Parameters
- - - - - - - - - - - +
NameDescription
T

The ulong value to write.

+
@@ -1055,7 +1149,8 @@
Type Parameters

PutArray(Array, int)

-
+

Serializes an Array prefixed with a 2-byte ushort length.

+
Declaration
@@ -1074,20 +1169,27 @@
Parameters
Array arr - +

The source array to serialize.

+ int sz - +

The size of a single element in bytes.

+ +
Remarks
+

If the array is null, a length of 0 is written.
+The total payload size is calculated as length * sz.

+

PutArray(bool[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1114,7 +1216,8 @@
Parameters

PutArray(double[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1141,7 +1244,8 @@
Parameters

PutArray(short[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1168,7 +1272,8 @@
Parameters

PutArray(int[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1195,7 +1300,8 @@
Parameters

PutArray(long[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1222,7 +1328,8 @@
Parameters

PutArray(float[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1248,12 +1355,13 @@
Parameters
-

PutArray(string[], int)

-
+

PutArray(string[])

+

Serializes an array of string values.

+
Declaration
-
public void PutArray(string[] value, int strMaxLength)
+
public void PutArray(string[] value)
Parameters
@@ -1268,24 +1376,24 @@
Parameters
- - - - - - +
string[] value
intstrMaxLength

The array of string elements to write.

+
+
Remarks
+

Writes a 2-byte ushort length header followed by each string element.

+
-

PutArray(string[])

-
+

PutArray(string[], int)

+

Serializes an array of string values with a maximum length constraint per element.

+
Declaration
-
public void PutArray(string[] value)
+
public void PutArray(string[] value, int strMaxLength)
Parameters
@@ -1300,7 +1408,14 @@
Parameters
- + + + + + +
string[] value

The array of string elements to write.

+
intstrMaxLength

The maximum allowed length for each individual string.

+
@@ -1308,7 +1423,8 @@
Parameters

PutArray(ushort[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1335,7 +1451,8 @@
Parameters

PutArray(uint[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1362,7 +1479,8 @@
Parameters

PutArray(ulong[])

-
+

Serializes an array of unmanaged values to the internal buffer.

+
Declaration
@@ -1387,9 +1505,84 @@
Parameters
+ +

PutArray<T>(T[])

+

Serializes an array of objects implementing INetSerializable.

+
+
+
Declaration
+
+
public void PutArray<T>(T[] value) where T : INetSerializable, new()
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
T[]value

The array of objects to serialize.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

A type that implements INetSerializable and has a parameterless constructor.

+
+ + + +

PutBytesWithLength(byte[])

+

Serializes a byte array prefixed with its ushort length.

+
+
+
Declaration
+
+
public void PutBytesWithLength(byte[] data)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
byte[]data

The source array.

+
+ +

PutBytesWithLength(byte[], int, ushort)

-
+

Serializes a segment of a byte array prefixed with its ushort length.

+
Declaration
@@ -1408,29 +1601,35 @@
Parameters
byte[] data - +

The source byte array.

+ int offset - +

The starting index in the source array.

+ ushort length - +

The number of bytes to write.

+ - -

PutBytesWithLength(byte[])

-
+ +

PutEnum<T>(T)

+

Writes an enum value of type T to the internal data buffer at the current position.
+Automatically resizes the buffer if LiteNetLib.Utils.NetDataWriter._autoResize is enabled. +Advances the position by the size of T.

+
Declaration
-
public void PutBytesWithLength(byte[] data)
+
public void PutEnum<T>(T value) where T : unmanaged, Enum
Parameters
@@ -1443,9 +1642,136 @@
Parameters
- + + + + + +
byte[]Tvalue

The enum value to write.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

An unmanaged enum type to write.

+
+ + + +

PutLargeString(string)

+

Serializes a string using a 4-byte int length header.

+
+
+
Declaration
+
+
public void PutLargeString(string value)
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
stringvalue

The string to write.

+
+
Remarks
+

Recommended for strings that may exceed the 65535 byte limit of standard ushort length headers.
+Uses UTF8.

+
+ + + +

PutNullableUnmanaged<T>(T?)

+

Writes a nullable value of type T into the internal byte buffer at the current position, +first writing a bool 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 T if the value is present.

+
+
+
Declaration
+
+
public void PutNullableUnmanaged<T>(T? value) where T : unmanaged
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
T?value

The nullable value to write into the buffer. If null, only a false flag is written.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

An unmanaged value type to write into the buffer.

+
+ + + +

PutSBytesWithLength(sbyte[])

+

Serializes an sbyte array prefixed with its ushort length.

+
+
+
Declaration
+
+
public void PutSBytesWithLength(sbyte[] data)
+
+
Parameters
+ + + + + + + + + + + - +
TypeNameDescription
sbyte[] data

The source array.

+
@@ -1453,7 +1779,8 @@
Parameters

PutSBytesWithLength(sbyte[], int, ushort)

-
+

Serializes a segment of an sbyte array prefixed with its ushort length.

+
Declaration
@@ -1472,29 +1799,33 @@
Parameters
sbyte[] data - +

The source sbyte array.

+ int offset - +

The starting index in the source array.

+ ushort length - +

The number of elements to write.

+ - -

PutSBytesWithLength(sbyte[])

-
+ +

PutSpan<T>(Span<T>)

+

Serializes a Span<T> of unmanaged values to the internal buffer.

+
Declaration
-
public void PutSBytesWithLength(sbyte[] data)
+
public void PutSpan<T>(Span<T> span) where T : unmanaged
Parameters
@@ -1507,17 +1838,177 @@
Parameters
- - - + + +
sbyte[]dataSpan<T>span

The span of data to write.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

The unmanaged type of the span elements.

+
+
Remarks
+

Writes a 2-byte ushort length header followed by the raw binary data.

+
+ + + +

PutUnmanagedArray<T>(T[])

+

Serializes an array of unmanaged values.

+
+
+
Declaration
+
+
public void PutUnmanagedArray<T>(T[] arr) where T : unmanaged
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
T[]arr

The array to serialize.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

The unmanaged type of the array elements.

+
+ + + +

PutUnmanaged<T>(T)

+

Writes a value of type T into the internal byte buffer at the current position, +advancing the position by the size of T.

+
+
+
Declaration
+
+
public void PutUnmanaged<T>(T value) where T : unmanaged
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
Tvalue

The value to write into the buffer.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

An unmanaged value type to write into the buffer.

+
+ + + +

Put<T>(T)

+

Serializes an object implementing INetSerializable.

+
+
+
Declaration
+
+
public void Put<T>(T obj) where T : INetSerializable
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
Tobj

The object instance to serialize.

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T

A type that implements the INetSerializable interface.

+
+
Remarks
+

This method calls the Serialize(NetDataWriter) method on the provided obj.

+

Reset()

-
+

Resets the _position to 0, effectively clearing the buffer for reuse.

+
Declaration
@@ -1527,7 +2018,8 @@
Declaration

Reset(int)

-
+

Resets the _position to 0 and ensures the internal buffer has at least the specified size.

+
Declaration
@@ -1546,15 +2038,20 @@
Parameters
int size - +

The minimum capacity required for the internal buffer.

+ +
Remarks
+

If the current buffer is smaller than size, ResizeIfNeed(int) will allocate a larger byte array.

+

ResizeIfNeed(int)

-
+

Ensures the internal buffer is at least newSize.

+
Declaration
@@ -1573,10 +2070,15 @@
Parameters
int newSize - +

The required minimum size of the buffer.

+ +
Remarks
+

If an allocation is necessary, the buffer grows to either newSize +or doubles its current size, whichever is larger.

+
@@ -1633,7 +2135,7 @@
Returns
diff --git a/docs/api/LiteNetLib.Utils.NetPacketProcessor.html b/docs/api/LiteNetLib.Utils.NetPacketProcessor.html index d15f536d..b80de24b 100644 --- a/docs/api/LiteNetLib.Utils.NetPacketProcessor.html +++ b/docs/api/LiteNetLib.Utils.NetPacketProcessor.html @@ -1,15 +1,14 @@ - + - Class NetPacketProcessor - + Class NetPacketProcessor - + + @@ -71,13 +70,12 @@ -

Class NetPacketProcessor -

+

Class NetPacketProcessor

Inheritance
- +
object
NetPacketProcessor
Namespace: LiteNetLib.Utils
@@ -164,7 +162,7 @@
Returns
- NetPacketProcessor.SubscribeDelegate + NetPacketProcessor.SubscribeDelegate @@ -212,13 +210,13 @@
Type Parameters
-

ReadAllPackets(NetDataReader, object)

+

ReadAllPackets(NetDataReader)

Reads all available data from NetDataReader and calls OnReceive delegates

Declaration
-
public void ReadAllPackets(NetDataReader reader, object userData)
+
public void ReadAllPackets(NetDataReader reader)
Parameters
@@ -234,28 +232,6 @@
Parameters
- - - - - - - -
NetDataReader reader

NetDataReader with packets data

-
objectuserData

Argument that passed to OnReceivedEvent

-
-
Exceptions
- - - - - - - - - - - @@ -263,13 +239,13 @@
Exceptions
-

ReadAllPackets(NetDataReader)

+

ReadAllPackets(NetDataReader, object)

Reads all available data from NetDataReader and calls OnReceive delegates

Declaration
-
public void ReadAllPackets(NetDataReader reader)
+
public void ReadAllPackets(NetDataReader reader, object userData)
Parameters
TypeCondition
ParseException

Malformed packet

@@ -285,6 +261,28 @@
Parameters
+ + + + + + + +
NetDataReader reader

NetDataReader with packets data

+
objectuserData

Argument that passed to OnReceivedEvent

+
+
Exceptions
+ + + + + + + + + + + @@ -292,13 +290,13 @@
Parameters
-

ReadPacket(NetDataReader, object)

+

ReadPacket(NetDataReader)

Reads one packet from NetDataReader and calls OnReceive delegate

Declaration
-
public void ReadPacket(NetDataReader reader, object userData)
+
public void ReadPacket(NetDataReader reader)
Parameters
TypeCondition
ParseException

Malformed packet

@@ -314,12 +312,6 @@
Parameters
- - - - - @@ -343,13 +335,13 @@
Exceptions
-

ReadPacket(NetDataReader)

+

ReadPacket(NetDataReader, object)

Reads one packet from NetDataReader and calls OnReceive delegate

Declaration
-
public void ReadPacket(NetDataReader reader)
+
public void ReadPacket(NetDataReader reader, object userData)
Parameters
NetDataReader reader

NetDataReader with packet

-
objectuserData

Argument that passed to OnReceivedEvent

@@ -365,6 +357,12 @@
Parameters
+ + + + + @@ -549,14 +547,14 @@
Type Parameters
NetDataReader reader

NetDataReader with packet

+
objectuserData

Argument that passed to OnReceivedEvent

- -

Subscribe<T>(Action<T>, Func<T>)

-

Register and subscribe to packet receive event

+ +

SubscribeNetSerializable<T>(Action<T>)

+

Registers a callback for a packet type that implements INetSerializable and has a parameterless constructor.

Declaration
-
public void Subscribe<T>(Action<T> onReceive, Func<T> packetConstructor) where T : class, new()
+
public void SubscribeNetSerializable<T>(Action<T> onReceive) where T : INetSerializable, new()
Parameters
@@ -571,13 +569,7 @@
Parameters
- - - - - - @@ -593,36 +585,24 @@
Type Parameters
- - - -
Action<T> onReceive

event that will be called when packet deserialized with ReadPacket method

-
Func<T>packetConstructor

Method that constructs packet instead of slow Activator.CreateInstance

+

The delegate to be executed when the packet is received.

T
-
Exceptions
- - - - - - - - - - -
TypeCondition
InvalidTypeException

T's fields are not supported, or it has no fields

+

The type of the packet. Must implement INetSerializable and have a new() constraint.

+
Remarks
+

To reduce allocations, this method uses a single internal reference to T for deserialization.

+
- -

Subscribe<T, TUserData>(Action<T, TUserData>, Func<T>)

-

Register and subscribe to packet receive event (with userData)

+ +

SubscribeNetSerializable<T>(Action<T>, Func<T>)

+

Registers a callback for a packet type that implements INetSerializable, using a custom constructor.

Declaration
-
public void Subscribe<T, TUserData>(Action<T, TUserData> onReceive, Func<T> packetConstructor) where T : class, new()
+
public void SubscribeNetSerializable<T>(Action<T> onReceive, Func<T> packetConstructor) where T : INetSerializable
Parameters
@@ -635,15 +615,15 @@
Parameters
- + - - @@ -659,26 +639,7 @@
Type Parameters
- - - - - - - -
Action<T, TUserData>Action<T> onReceive

event that will be called when packet deserialized with ReadPacket method

+

The delegate to be executed when the packet is received.

Func<T> packetConstructor

Method that constructs packet instead of slow Activator.CreateInstance

+

A function that returns a new instance of T.

T
TUserData
-
Exceptions
- - - - - - - - - - - @@ -686,12 +647,13 @@
Exceptions
-

SubscribeNetSerializable<T>(Action<T>, Func<T>)

-
+

SubscribeNetSerializable<T, TUserData>(Action<T, TUserData>)

+

Registers a callback for a packet type that implements INetSerializable and has a parameterless constructor, supporting user data.

+
Declaration
-
public void SubscribeNetSerializable<T>(Action<T> onReceive, Func<T> packetConstructor) where T : INetSerializable
+
public void SubscribeNetSerializable<T, TUserData>(Action<T, TUserData> onReceive) where T : INetSerializable, new()
Parameters
TypeCondition
InvalidTypeException

T's fields are not supported, or it has no fields

+

The type of the packet. Must implement INetSerializable.

@@ -704,14 +666,10 @@
Parameters
- + - - - - - - +
Action<T>Action<T, TUserData> onReceive
Func<T>packetConstructor

The delegate to be executed when the packet is received.

+
@@ -726,19 +684,29 @@
Type Parameters
T - +

The type of the packet. Must implement INetSerializable and have a new() constraint.

+ + + + TUserData +

The type of the user data (typically NetPeer).

+ +
Remarks
+

To reduce allocations, this method uses a single internal reference to T for deserialization.

+
-

SubscribeNetSerializable<T>(Action<T>)

-
+

SubscribeNetSerializable<T, TUserData>(Action<T, TUserData>, Func<T>)

+

Registers a callback for a packet type that implements INetSerializable, using a custom constructor and supporting user data.

+
Declaration
-
public void SubscribeNetSerializable<T>(Action<T> onReceive) where T : INetSerializable, new()
+
public void SubscribeNetSerializable<T, TUserData>(Action<T, TUserData> onReceive, Func<T> packetConstructor) where T : INetSerializable
Parameters
@@ -751,9 +719,16 @@
Parameters
- + - + + + + + +
Action<T>Action<T, TUserData> onReceive

The delegate to be executed when the packet is received.

+
Func<T>packetConstructor

A function that returns a new instance of T.

+
@@ -768,19 +743,27 @@
Type Parameters
T - +

The type of the packet. Must implement INetSerializable.

+ + + + TUserData +

The type of the user data (typically NetPeer).

+ - -

SubscribeNetSerializable<T, TUserData>(Action<T, TUserData>, Func<T>)

-
+ +

SubscribeReusable<T>(Action<T>)

+

Register and subscribe to packet receive event +This method will overwrite last received packet class on receive (less garbage)

+
Declaration
-
public void SubscribeNetSerializable<T, TUserData>(Action<T, TUserData> onReceive, Func<T> packetConstructor) where T : INetSerializable
+
public void SubscribeReusable<T>(Action<T> onReceive) where T : class, new()
Parameters
@@ -793,14 +776,10 @@
Parameters
- + - - - - - - +
Action<T, TUserData>Action<T> onReceive
Func<T>packetConstructor

event that will be called when packet deserialized with ReadPacket method

+
@@ -817,21 +796,35 @@
Type Parameters
T + + +
Exceptions
+ + - - + + + + + + + +
TUserDataTypeCondition
InvalidTypeException

T's fields are not supported, or it has no fields

+
- -

SubscribeNetSerializable<T, TUserData>(Action<T, TUserData>)

-
+ +

SubscribeReusable<T, TUserData>(Action<T, TUserData>)

+

Register and subscribe to packet receive event +This method will overwrite last received packet class on receive (less garbage)

+
Declaration
-
public void SubscribeNetSerializable<T, TUserData>(Action<T, TUserData> onReceive) where T : INetSerializable, new()
+
public void SubscribeReusable<T, TUserData>(Action<T, TUserData> onReceive) where T : class, new()
Parameters
@@ -846,7 +839,8 @@
Parameters
- +
Action<T, TUserData> onReceive

event that will be called when packet deserialized with ReadPacket method

+
@@ -869,17 +863,32 @@
Type Parameters
+
Exceptions
+ + + + + + + + + + + + + +
TypeCondition
InvalidTypeException

T's fields are not supported, or it has no fields

+
- -

SubscribeReusable<T>(Action<T>)

-

Register and subscribe to packet receive event -This method will overwrite last received packet class on receive (less garbage)

+ +

Subscribe<T>(Action<T>, Func<T>)

+

Register and subscribe to packet receive event

Declaration
-
public void SubscribeReusable<T>(Action<T> onReceive) where T : class, new()
+
public void Subscribe<T>(Action<T> onReceive, Func<T> packetConstructor) where T : class, new()
Parameters
@@ -895,6 +904,12 @@
Parameters
+ + + + + @@ -932,15 +947,14 @@
Exceptions
Action<T> onReceive

event that will be called when packet deserialized with ReadPacket method

+
Func<T>packetConstructor

Method that constructs packet instead of slow Activator.CreateInstance

- -

SubscribeReusable<T, TUserData>(Action<T, TUserData>)

-

Register and subscribe to packet receive event -This method will overwrite last received packet class on receive (less garbage)

+ +

Subscribe<T, TUserData>(Action<T, TUserData>, Func<T>)

+

Register and subscribe to packet receive event (with userData)

Declaration
-
public void SubscribeReusable<T, TUserData>(Action<T, TUserData> onReceive) where T : class, new()
+
public void Subscribe<T, TUserData>(Action<T, TUserData> onReceive, Func<T> packetConstructor) where T : class, new()
Parameters
@@ -956,6 +970,12 @@
Parameters
+ + + + + @@ -997,13 +1017,13 @@
Exceptions
Action<T, TUserData> onReceive

event that will be called when packet deserialized with ReadPacket method

+
Func<T>packetConstructor

Method that constructs packet instead of slow Activator.CreateInstance

- -

Write<T>(NetDataWriter, T)

+ +

WriteHash<T>(NetDataWriter)

Declaration
-
public void Write<T>(NetDataWriter writer, T packet) where T : class, new()
+
protected virtual void WriteHash<T>(NetDataWriter writer)
Parameters
@@ -1020,11 +1040,6 @@
Parameters
- - - - -
writer
Tpacket
Type Parameters
@@ -1044,13 +1059,13 @@
Type Parameters
- -

WriteHash<T>(NetDataWriter)

+ +

WriteNetSerializable<T>(NetDataWriter, ref T)

Declaration
-
protected virtual void WriteHash<T>(NetDataWriter writer)
+
public void WriteNetSerializable<T>(NetDataWriter writer, ref T packet) where T : INetSerializable
Parameters
@@ -1067,6 +1082,11 @@
Parameters
+ + + + +
writer
Tpacket
Type Parameters
@@ -1086,13 +1106,13 @@
Type Parameters
- -

WriteNetSerializable<T>(NetDataWriter, ref T)

+ +

Write<T>(NetDataWriter, T)

Declaration
-
public void WriteNetSerializable<T>(NetDataWriter writer, ref T packet) where T : INetSerializable
+
public void Write<T>(NetDataWriter writer, T packet) where T : class, new()
Parameters
@@ -1142,7 +1162,7 @@
Type Parameters
diff --git a/docs/api/LiteNetLib.Utils.NetSerializer.html b/docs/api/LiteNetLib.Utils.NetSerializer.html index 15d99753..3cc35124 100644 --- a/docs/api/LiteNetLib.Utils.NetSerializer.html +++ b/docs/api/LiteNetLib.Utils.NetSerializer.html @@ -1,15 +1,14 @@ - + - Class NetSerializer - + Class NetSerializer - + + @@ -71,13 +70,12 @@ -

Class NetSerializer -

+

Class NetSerializer

Inheritance
- +
object
NetSerializer
Namespace: LiteNetLib.Utils
@@ -130,13 +128,13 @@

Methods -

Deserialize<T>(NetDataReader, T)

-

Reads packet with known type (non alloc variant)

+

Deserialize<T>(NetDataReader)

+

Reads packet with known type

Declaration
-
public bool Deserialize<T>(NetDataReader reader, T target) where T : class, new()
+
public T Deserialize<T>(NetDataReader reader) where T : class, new()
Parameters
@@ -152,12 +150,6 @@
Parameters
- - - - - @@ -172,8 +164,8 @@
Returns
- - + @@ -212,13 +204,13 @@
Exceptions
-

Deserialize<T>(NetDataReader)

-

Reads packet with known type

+

Deserialize<T>(NetDataReader, T)

+

Reads packet with known type (non alloc variant)

Declaration
-
public T Deserialize<T>(NetDataReader reader) where T : class, new()
+
public bool Deserialize<T>(NetDataReader reader, T target) where T : class, new()
Parameters
NetDataReader reader

NetDataReader with packet

-
Ttarget

Deserialization target

bool

Returns true if packet in reader is matched type

+
T

Returns packet if packet in reader is matched type

@@ -236,65 +228,30 @@
Parameters
- -

NetDataReader with packet

-
Returns
- - - - - - - - - +
TypeDescription
T

Returns packet if packet in reader is matched type

+
target

Deserialization target

-
Type Parameters
- - - - - - - - - - - - - -
NameDescription
T
-
Exceptions
+
Returns
- + - - +
TypeConditionDescription
InvalidTypeException

T's fields are not supported, or it has no fields

+
bool

Returns true if packet in reader is matched type

- - - -

Register<T>()

-
-
-
Declaration
-
-
public void Register<T>()
-
Type Parameters
@@ -450,64 +407,45 @@
Type Parameters
- -

Serialize<T>(T)

-

Serialize object to byte array

-
+ +

Register<T>()

+
Declaration
-
public byte[] Serialize<T>(T obj) where T : class, new()
+
public void Register<T>()
-
Parameters
+
Type Parameters
- - - - + +
Type Name Description
Tobj

Object to serialize

-
T
-
Returns
+
Exceptions
- + - - +
TypeDescriptionCondition
byte[]

byte array with serialized data

+
InvalidTypeException

T's fields are not supported, or it has no fields

-
Type Parameters
- - - - - - - - - - - - - -
NameDescription
T
@@ -575,6 +513,66 @@
Exceptions
+ + +

Serialize<T>(T)

+

Serialize object to byte array

+
+
+
Declaration
+
+
public byte[] Serialize<T>(T obj) where T : class, new()
+
+
Parameters
+ + + + + + + + + + + + + + + +
TypeNameDescription
Tobj

Object to serialize

+
+
Returns
+ + + + + + + + + + + + + +
TypeDescription
byte[]

byte array with serialized data

+
+
Type Parameters
+ + + + + + + + + + + + + +
NameDescription
T
+
@@ -585,7 +583,7 @@
Exceptions
diff --git a/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html b/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html index a41b662d..6ab9dbab 100644 --- a/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html +++ b/docs/api/LiteNetLib.Utils.NtpLeapIndicator.html @@ -1,16 +1,15 @@ - + - Enum NtpLeapIndicator - + Enum NtpLeapIndicator - + + @@ -72,8 +71,7 @@ -

Enum NtpLeapIndicator -

+

Enum NtpLeapIndicator

Represents leap second warning from the server that instructs the client to add or remove leap second.

@@ -92,7 +90,7 @@

Fields Description - + AlarmCondition

Special value indicating that the server clock is unsynchronized and the returned time is unreliable.

@@ -114,7 +112,7 @@

Fields - +

See Also
@@ -129,7 +127,7 @@
See Also
diff --git a/docs/api/LiteNetLib.Utils.NtpMode.html b/docs/api/LiteNetLib.Utils.NtpMode.html index 7a203a2c..55f84b00 100644 --- a/docs/api/LiteNetLib.Utils.NtpMode.html +++ b/docs/api/LiteNetLib.Utils.NtpMode.html @@ -1,16 +1,15 @@ - + - Enum NtpMode - + Enum NtpMode - + + @@ -72,8 +71,7 @@ -

Enum NtpMode -

+

Enum NtpMode

Describes SNTP packet mode, i.e. client or server.

@@ -92,7 +90,7 @@

Fields Description - + Client

Identifies client-to-server SNTP packet.

@@ -104,7 +102,7 @@

Fields - +

See Also
@@ -119,7 +117,7 @@
See Also
diff --git a/docs/api/LiteNetLib.Utils.NtpPacket.html b/docs/api/LiteNetLib.Utils.NtpPacket.html index 313c36cf..d63393a4 100644 --- a/docs/api/LiteNetLib.Utils.NtpPacket.html +++ b/docs/api/LiteNetLib.Utils.NtpPacket.html @@ -1,16 +1,15 @@ - + - Class NtpPacket - + Class NtpPacket - + + @@ -71,14 +70,13 @@ -

Class NtpPacket -

+

Class NtpPacket

Represents RFC4330 SNTP packet used for communication to and from a network time server.

Inheritance
- +
object
NtpPacket
Namespace: LiteNetLib.Utils
@@ -731,7 +729,7 @@
Returns
diff --git a/docs/api/LiteNetLib.Utils.ParseException.html b/docs/api/LiteNetLib.Utils.ParseException.html index f3659548..34652750 100644 --- a/docs/api/LiteNetLib.Utils.ParseException.html +++ b/docs/api/LiteNetLib.Utils.ParseException.html @@ -1,15 +1,14 @@ - + - Class ParseException - + Class ParseException - + + @@ -71,13 +70,12 @@ -

Class ParseException -

+

Class ParseException

Inheritance
- +
object
ParseException
@@ -177,7 +175,7 @@

Implements

diff --git a/docs/api/LiteNetLib.Utils.PreserveAttribute.html b/docs/api/LiteNetLib.Utils.PreserveAttribute.html new file mode 100644 index 00000000..d6063883 --- /dev/null +++ b/docs/api/LiteNetLib.Utils.PreserveAttribute.html @@ -0,0 +1,241 @@ + + + + + + + + Class PreserveAttribute + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + diff --git a/docs/api/LiteNetLib.Utils.html b/docs/api/LiteNetLib.Utils.html index f079835d..344fbc61 100644 --- a/docs/api/LiteNetLib.Utils.html +++ b/docs/api/LiteNetLib.Utils.html @@ -1,15 +1,14 @@ - + - Namespace LiteNetLib.Utils - + Namespace LiteNetLib.Utils - + + @@ -69,12 +68,12 @@
-

Namespace LiteNetLib.Utils -

+

Namespace LiteNetLib.Utils

-

Classes +

+Classes

CRC32C

@@ -95,11 +94,17 @@

NtpPacket

ParseException

-

Interfaces +

PreserveAttribute

+

PreserveAttribute prevents byte code stripping from removing a class, method, field, or property.

+
+

+Interfaces

INetSerializable

-
-

Enums +

Interface for implementing custom data serialization for network transmission.

+
+

+Enums

NtpLeapIndicator

Represents leap second warning from the server that instructs the client to add or remove leap second.

@@ -119,7 +124,7 @@

NtpMode

diff --git a/docs/api/LiteNetLib.html b/docs/api/LiteNetLib.html index 9e5bc4e8..323dc0da 100644 --- a/docs/api/LiteNetLib.html +++ b/docs/api/LiteNetLib.html @@ -1,15 +1,14 @@ - + - Namespace LiteNetLib - + Namespace LiteNetLib - + + @@ -69,71 +68,92 @@
-

Namespace LiteNetLib -

+

Namespace LiteNetLib

-

Classes +

+Classes

ConnectionRequest

+

EventBasedLiteNetListener

+

Simple event based listener for simple setups and benchmarks

+

EventBasedNatPunchListener

-
+

An implementation of INatPunchListener that maps callbacks to events.

+

EventBasedNetListener

-
+

Simple event based listener for simple setups and benchmarks

+

InvalidPacketException

+

LiteConnectionRequest

+
+

LiteNetManager

+

Main class for all network operations. Can be used as client and/or server.

+
+

LiteNetPeer

+

Network peer. Main purpose is sending messages to specific peer.

+

NatPunchModule

Module for UDP NAT Hole punching operations. Can be accessed from NetManager

+

NetConnectRequestPacket

+

NetConstants

Network constants. Can be tuned from sources for your purposes.

NetDebug

Static class for defining your own LiteNetLib logger instead of Console.WriteLine or Debug.Log if compiled with UNITY flag

+
+

NetEvent

+

Internally used event type

NetManager

-

Main class for all network operations. Can be used as client and/or server.

+

More feature rich network manager with adjustable channels count

NetPacketReader

NetPeer

-

Network peer. Main purpose is sending messages to specific peer.

+

Improved LiteNetPeer with full multi-channel support

NetStatistics

-
+

Thread-safe counter for network statistics including sent/received packets, bytes, and packet loss.

+

NetUtils

Some specific network utilities

TooBigPacketException

-

Structs +

+Structs

DisconnectInfo

Additional information about disconnection

-

NetManager.NetPeerEnumerator

+

LiteNetManager.NetPeerEnumerator<T>

PooledPacket

-

Interfaces +

+Interfaces

-

IDeliveryEventListener

-
+

ILiteNetEventListener

+

Interface for implementing own ILiteNetEventListener. This is a bit faster than use EventBasedListener

+

INatPunchListener

-
+

Interface for handling events related to NAT punchthrough and introduction.

+

INetEventListener

-
+

Interface for implementing own INetEventListener. This is a bit faster than use EventBasedListener

+

INetLogger

Interface to implement for your own logger

-

INtpEventListener

-
-

IPeerAddressChangedListener

-
-

Enums +

+Enums

ConnectionState

Peer connection state

@@ -148,7 +168,11 @@

LocalAddrType

Address type that you want to receive from NetUtils.GetLocalIp method

NatAddressType

-
+

Specifies the type of network address discovered during NAT punchthrough.

+
+

NetEvent.EType

+

Specifies the category of the network event.

+

NetLogLevel

UnconnectedMessageType

@@ -166,7 +190,7 @@

UnconnectedMes

diff --git a/docs/api/index.html b/docs/api/index.html index 85e708e6..d95b666e 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -1,4 +1,4 @@ - + @@ -9,6 +9,7 @@ + @@ -68,6 +69,7 @@

LiteNetLib

+
@@ -78,7 +80,7 @@

LiteNetLib

diff --git a/docs/api/toc.html b/docs/api/toc.html index ea4c7816..290589f2 100644 --- a/docs/api/toc.html +++ b/docs/api/toc.html @@ -1,11 +1,11 @@ - + -
\ No newline at end of file +
diff --git a/docs/api/toc.json b/docs/api/toc.json new file mode 100644 index 00000000..17d2e194 --- /dev/null +++ b/docs/api/toc.json @@ -0,0 +1,2 @@ + +{"items":[{"name":"LiteNetLib","href":"LiteNetLib.html","topicHref":"LiteNetLib.html","topicUid":"LiteNetLib","type":"Namespace","items":[{"name":"ConnectionRequest","href":"LiteNetLib.ConnectionRequest.html","topicHref":"LiteNetLib.ConnectionRequest.html","topicUid":"LiteNetLib.ConnectionRequest","type":"Class"},{"name":"ConnectionState","href":"LiteNetLib.ConnectionState.html","topicHref":"LiteNetLib.ConnectionState.html","topicUid":"LiteNetLib.ConnectionState","type":"Enum"},{"name":"DeliveryMethod","href":"LiteNetLib.DeliveryMethod.html","topicHref":"LiteNetLib.DeliveryMethod.html","topicUid":"LiteNetLib.DeliveryMethod","type":"Enum"},{"name":"DisconnectInfo","href":"LiteNetLib.DisconnectInfo.html","topicHref":"LiteNetLib.DisconnectInfo.html","topicUid":"LiteNetLib.DisconnectInfo","type":"Struct"},{"name":"DisconnectReason","href":"LiteNetLib.DisconnectReason.html","topicHref":"LiteNetLib.DisconnectReason.html","topicUid":"LiteNetLib.DisconnectReason","type":"Enum"},{"name":"EventBasedLiteNetListener","href":"LiteNetLib.EventBasedLiteNetListener.html","topicHref":"LiteNetLib.EventBasedLiteNetListener.html","topicUid":"LiteNetLib.EventBasedLiteNetListener","type":"Class"},{"name":"EventBasedNatPunchListener","href":"LiteNetLib.EventBasedNatPunchListener.html","topicHref":"LiteNetLib.EventBasedNatPunchListener.html","topicUid":"LiteNetLib.EventBasedNatPunchListener","type":"Class"},{"name":"EventBasedNetListener","href":"LiteNetLib.EventBasedNetListener.html","topicHref":"LiteNetLib.EventBasedNetListener.html","topicUid":"LiteNetLib.EventBasedNetListener","type":"Class"},{"name":"ILiteNetEventListener","href":"LiteNetLib.ILiteNetEventListener.html","topicHref":"LiteNetLib.ILiteNetEventListener.html","topicUid":"LiteNetLib.ILiteNetEventListener","type":"Interface"},{"name":"INatPunchListener","href":"LiteNetLib.INatPunchListener.html","topicHref":"LiteNetLib.INatPunchListener.html","topicUid":"LiteNetLib.INatPunchListener","type":"Interface"},{"name":"INetEventListener","href":"LiteNetLib.INetEventListener.html","topicHref":"LiteNetLib.INetEventListener.html","topicUid":"LiteNetLib.INetEventListener","type":"Interface"},{"name":"INetLogger","href":"LiteNetLib.INetLogger.html","topicHref":"LiteNetLib.INetLogger.html","topicUid":"LiteNetLib.INetLogger","type":"Interface"},{"name":"InvalidPacketException","href":"LiteNetLib.InvalidPacketException.html","topicHref":"LiteNetLib.InvalidPacketException.html","topicUid":"LiteNetLib.InvalidPacketException","type":"Class"},{"name":"LiteConnectionRequest","href":"LiteNetLib.LiteConnectionRequest.html","topicHref":"LiteNetLib.LiteConnectionRequest.html","topicUid":"LiteNetLib.LiteConnectionRequest","type":"Class"},{"name":"LiteNetManager","href":"LiteNetLib.LiteNetManager.html","topicHref":"LiteNetLib.LiteNetManager.html","topicUid":"LiteNetLib.LiteNetManager","type":"Class"},{"name":"LiteNetManager.NetPeerEnumerator","href":"LiteNetLib.LiteNetManager.NetPeerEnumerator-1.html","topicHref":"LiteNetLib.LiteNetManager.NetPeerEnumerator-1.html","topicUid":"LiteNetLib.LiteNetManager.NetPeerEnumerator`1","type":"Struct"},{"name":"LiteNetPeer","href":"LiteNetLib.LiteNetPeer.html","topicHref":"LiteNetLib.LiteNetPeer.html","topicUid":"LiteNetLib.LiteNetPeer","type":"Class"},{"name":"LocalAddrType","href":"LiteNetLib.LocalAddrType.html","topicHref":"LiteNetLib.LocalAddrType.html","topicUid":"LiteNetLib.LocalAddrType","type":"Enum"},{"name":"NatAddressType","href":"LiteNetLib.NatAddressType.html","topicHref":"LiteNetLib.NatAddressType.html","topicUid":"LiteNetLib.NatAddressType","type":"Enum"},{"name":"NatPunchModule","href":"LiteNetLib.NatPunchModule.html","topicHref":"LiteNetLib.NatPunchModule.html","topicUid":"LiteNetLib.NatPunchModule","type":"Class"},{"name":"NetConnectRequestPacket","href":"LiteNetLib.NetConnectRequestPacket.html","topicHref":"LiteNetLib.NetConnectRequestPacket.html","topicUid":"LiteNetLib.NetConnectRequestPacket","type":"Class"},{"name":"NetConstants","href":"LiteNetLib.NetConstants.html","topicHref":"LiteNetLib.NetConstants.html","topicUid":"LiteNetLib.NetConstants","type":"Class"},{"name":"NetDebug","href":"LiteNetLib.NetDebug.html","topicHref":"LiteNetLib.NetDebug.html","topicUid":"LiteNetLib.NetDebug","type":"Class"},{"name":"NetEvent","href":"LiteNetLib.NetEvent.html","topicHref":"LiteNetLib.NetEvent.html","topicUid":"LiteNetLib.NetEvent","type":"Class"},{"name":"NetEvent.EType","href":"LiteNetLib.NetEvent.EType.html","topicHref":"LiteNetLib.NetEvent.EType.html","topicUid":"LiteNetLib.NetEvent.EType","type":"Enum"},{"name":"NetLogLevel","href":"LiteNetLib.NetLogLevel.html","topicHref":"LiteNetLib.NetLogLevel.html","topicUid":"LiteNetLib.NetLogLevel","type":"Enum"},{"name":"NetManager","href":"LiteNetLib.NetManager.html","topicHref":"LiteNetLib.NetManager.html","topicUid":"LiteNetLib.NetManager","type":"Class"},{"name":"NetPacketReader","href":"LiteNetLib.NetPacketReader.html","topicHref":"LiteNetLib.NetPacketReader.html","topicUid":"LiteNetLib.NetPacketReader","type":"Class"},{"name":"NetPeer","href":"LiteNetLib.NetPeer.html","topicHref":"LiteNetLib.NetPeer.html","topicUid":"LiteNetLib.NetPeer","type":"Class"},{"name":"NetStatistics","href":"LiteNetLib.NetStatistics.html","topicHref":"LiteNetLib.NetStatistics.html","topicUid":"LiteNetLib.NetStatistics","type":"Class"},{"name":"NetUtils","href":"LiteNetLib.NetUtils.html","topicHref":"LiteNetLib.NetUtils.html","topicUid":"LiteNetLib.NetUtils","type":"Class"},{"name":"PooledPacket","href":"LiteNetLib.PooledPacket.html","topicHref":"LiteNetLib.PooledPacket.html","topicUid":"LiteNetLib.PooledPacket","type":"Struct"},{"name":"TooBigPacketException","href":"LiteNetLib.TooBigPacketException.html","topicHref":"LiteNetLib.TooBigPacketException.html","topicUid":"LiteNetLib.TooBigPacketException","type":"Class"},{"name":"UnconnectedMessageType","href":"LiteNetLib.UnconnectedMessageType.html","topicHref":"LiteNetLib.UnconnectedMessageType.html","topicUid":"LiteNetLib.UnconnectedMessageType","type":"Enum"}]},{"name":"LiteNetLib.Layers","href":"LiteNetLib.Layers.html","topicHref":"LiteNetLib.Layers.html","topicUid":"LiteNetLib.Layers","type":"Namespace","items":[{"name":"Crc32cLayer","href":"LiteNetLib.Layers.Crc32cLayer.html","topicHref":"LiteNetLib.Layers.Crc32cLayer.html","topicUid":"LiteNetLib.Layers.Crc32cLayer","type":"Class"},{"name":"PacketLayerBase","href":"LiteNetLib.Layers.PacketLayerBase.html","topicHref":"LiteNetLib.Layers.PacketLayerBase.html","topicUid":"LiteNetLib.Layers.PacketLayerBase","type":"Class"},{"name":"XorEncryptLayer","href":"LiteNetLib.Layers.XorEncryptLayer.html","topicHref":"LiteNetLib.Layers.XorEncryptLayer.html","topicUid":"LiteNetLib.Layers.XorEncryptLayer","type":"Class"}]},{"name":"LiteNetLib.Utils","href":"LiteNetLib.Utils.html","topicHref":"LiteNetLib.Utils.html","topicUid":"LiteNetLib.Utils","type":"Namespace","items":[{"name":"CRC32C","href":"LiteNetLib.Utils.CRC32C.html","topicHref":"LiteNetLib.Utils.CRC32C.html","topicUid":"LiteNetLib.Utils.CRC32C","type":"Class"},{"name":"FastBitConverter","href":"LiteNetLib.Utils.FastBitConverter.html","topicHref":"LiteNetLib.Utils.FastBitConverter.html","topicUid":"LiteNetLib.Utils.FastBitConverter","type":"Class"},{"name":"INetSerializable","href":"LiteNetLib.Utils.INetSerializable.html","topicHref":"LiteNetLib.Utils.INetSerializable.html","topicUid":"LiteNetLib.Utils.INetSerializable","type":"Interface"},{"name":"InvalidTypeException","href":"LiteNetLib.Utils.InvalidTypeException.html","topicHref":"LiteNetLib.Utils.InvalidTypeException.html","topicUid":"LiteNetLib.Utils.InvalidTypeException","type":"Class"},{"name":"NetDataReader","href":"LiteNetLib.Utils.NetDataReader.html","topicHref":"LiteNetLib.Utils.NetDataReader.html","topicUid":"LiteNetLib.Utils.NetDataReader","type":"Class"},{"name":"NetDataWriter","href":"LiteNetLib.Utils.NetDataWriter.html","topicHref":"LiteNetLib.Utils.NetDataWriter.html","topicUid":"LiteNetLib.Utils.NetDataWriter","type":"Class"},{"name":"NetPacketProcessor","href":"LiteNetLib.Utils.NetPacketProcessor.html","topicHref":"LiteNetLib.Utils.NetPacketProcessor.html","topicUid":"LiteNetLib.Utils.NetPacketProcessor","type":"Class"},{"name":"NetSerializer","href":"LiteNetLib.Utils.NetSerializer.html","topicHref":"LiteNetLib.Utils.NetSerializer.html","topicUid":"LiteNetLib.Utils.NetSerializer","type":"Class"},{"name":"NtpLeapIndicator","href":"LiteNetLib.Utils.NtpLeapIndicator.html","topicHref":"LiteNetLib.Utils.NtpLeapIndicator.html","topicUid":"LiteNetLib.Utils.NtpLeapIndicator","type":"Enum"},{"name":"NtpMode","href":"LiteNetLib.Utils.NtpMode.html","topicHref":"LiteNetLib.Utils.NtpMode.html","topicUid":"LiteNetLib.Utils.NtpMode","type":"Enum"},{"name":"NtpPacket","href":"LiteNetLib.Utils.NtpPacket.html","topicHref":"LiteNetLib.Utils.NtpPacket.html","topicUid":"LiteNetLib.Utils.NtpPacket","type":"Class"},{"name":"ParseException","href":"LiteNetLib.Utils.ParseException.html","topicHref":"LiteNetLib.Utils.ParseException.html","topicUid":"LiteNetLib.Utils.ParseException","type":"Class"},{"name":"PreserveAttribute","href":"LiteNetLib.Utils.PreserveAttribute.html","topicHref":"LiteNetLib.Utils.PreserveAttribute.html","topicUid":"LiteNetLib.Utils.PreserveAttribute","type":"Class"}]}],"memberLayout":"SamePage"} diff --git a/docs/articles/netserializerusage.html b/docs/articles/netserializerusage.html index aaae5e4f..16a66a14 100644 --- a/docs/articles/netserializerusage.html +++ b/docs/articles/netserializerusage.html @@ -1,4 +1,4 @@ - + @@ -9,6 +9,7 @@ + @@ -227,6 +228,7 @@

Mini FAQ

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.

+
@@ -237,7 +239,7 @@

Mini FAQ

diff --git a/docs/articles/toc.html b/docs/articles/toc.html index db355290..dd5b5ec1 100644 --- a/docs/articles/toc.html +++ b/docs/articles/toc.html @@ -1,11 +1,11 @@ - +
- +
@@ -19,4 +19,4 @@
-
\ No newline at end of file +
diff --git a/docs/articles/toc.json b/docs/articles/toc.json new file mode 100644 index 00000000..a67e5ac5 --- /dev/null +++ b/docs/articles/toc.json @@ -0,0 +1,2 @@ + +{"items":[{"name":"NetSerializer usage","href":"netserializerusage.html","topicHref":"netserializerusage.html"}]} diff --git a/docs/favicon.ico b/docs/favicon.ico old mode 100644 new mode 100755 diff --git a/docs/index.html b/docs/index.html index ea6e98b2..189405fb 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,4 +1,4 @@ - + @@ -9,6 +9,7 @@ + @@ -193,13 +194,13 @@

NetManager settings description

  • 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
  • @@ -246,6 +247,7 @@

    NetManager settings description

    +
    @@ -256,7 +258,7 @@

    NetManager settings description

    diff --git a/docs/logo.svg b/docs/logo.svg old mode 100644 new mode 100755 diff --git a/docs/manifest.json b/docs/manifest.json index 2a4a8307..3d87716c 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1,6 +1,5 @@ { - "homepages": [], - "source_base_path": "E:/Projects/LiteNetLib/docfx_project", + "source_base_path": "/home/revenantx/Projects/LiteNetLib/docfx_project", "xrefmap": "xrefmap.yml", "files": [ { @@ -11,7 +10,11 @@ "relative_path": "api/LiteNetLib.ConnectionRequest.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.ConnectionRequest", + "Summary": null }, { "type": "ManagedReference", @@ -21,7 +24,11 @@ "relative_path": "api/LiteNetLib.ConnectionState.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.ConnectionState", + "Summary": "

    Peer connection state

    \n" }, { "type": "ManagedReference", @@ -31,7 +38,11 @@ "relative_path": "api/LiteNetLib.DeliveryMethod.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.DeliveryMethod", + "Summary": "

    Sending method type

    \n" }, { "type": "ManagedReference", @@ -41,7 +52,11 @@ "relative_path": "api/LiteNetLib.DisconnectInfo.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.DisconnectInfo", + "Summary": "

    Additional information about disconnection

    \n" }, { "type": "ManagedReference", @@ -51,7 +66,25 @@ "relative_path": "api/LiteNetLib.DisconnectReason.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.DisconnectReason", + "Summary": "

    Disconnect reason that you receive in OnPeerDisconnected event

    \n" + }, + { + "type": "ManagedReference", + "source_relative_path": "api/LiteNetLib.EventBasedLiteNetListener.yml", + "output": { + ".html": { + "relative_path": "api/LiteNetLib.EventBasedLiteNetListener.html" + } + }, + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.EventBasedLiteNetListener", + "Summary": "

    Simple event based listener for simple setups and benchmarks

    \n" }, { "type": "ManagedReference", @@ -61,7 +94,11 @@ "relative_path": "api/LiteNetLib.EventBasedNatPunchListener.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.EventBasedNatPunchListener", + "Summary": "

    An implementation of that maps callbacks to events.

    \n" }, { "type": "ManagedReference", @@ -71,17 +108,25 @@ "relative_path": "api/LiteNetLib.EventBasedNetListener.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.EventBasedNetListener", + "Summary": "

    Simple event based listener for simple setups and benchmarks

    \n" }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.IDeliveryEventListener.yml", + "source_relative_path": "api/LiteNetLib.ILiteNetEventListener.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.IDeliveryEventListener.html" + "relative_path": "api/LiteNetLib.ILiteNetEventListener.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.ILiteNetEventListener", + "Summary": "

    Interface for implementing own ILiteNetEventListener. This is a bit faster than use EventBasedListener

    \n" }, { "type": "ManagedReference", @@ -91,7 +136,11 @@ "relative_path": "api/LiteNetLib.INatPunchListener.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.INatPunchListener", + "Summary": "

    Interface for handling events related to NAT punchthrough and introduction.

    \n" }, { "type": "ManagedReference", @@ -101,7 +150,11 @@ "relative_path": "api/LiteNetLib.INetEventListener.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.INetEventListener", + "Summary": "

    Interface for implementing own INetEventListener. This is a bit faster than use EventBasedListener

    \n" }, { "type": "ManagedReference", @@ -111,87 +164,151 @@ "relative_path": "api/LiteNetLib.INetLogger.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.INetLogger", + "Summary": "

    Interface to implement for your own logger

    \n" }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.INtpEventListener.yml", + "source_relative_path": "api/LiteNetLib.InvalidPacketException.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.INtpEventListener.html" + "relative_path": "api/LiteNetLib.InvalidPacketException.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.InvalidPacketException", + "Summary": null }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.IPeerAddressChangedListener.yml", + "source_relative_path": "api/LiteNetLib.Layers.Crc32cLayer.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.IPeerAddressChangedListener.html" + "relative_path": "api/LiteNetLib.Layers.Crc32cLayer.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Layers.Crc32cLayer", + "Summary": null }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.IPv6Mode.yml", + "source_relative_path": "api/LiteNetLib.Layers.PacketLayerBase.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.IPv6Mode.html" + "relative_path": "api/LiteNetLib.Layers.PacketLayerBase.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Layers.PacketLayerBase", + "Summary": null }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.InvalidPacketException.yml", + "source_relative_path": "api/LiteNetLib.Layers.XorEncryptLayer.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.InvalidPacketException.html" + "relative_path": "api/LiteNetLib.Layers.XorEncryptLayer.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Layers.XorEncryptLayer", + "Summary": null }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.Layers.Crc32cLayer.yml", + "source_relative_path": "api/LiteNetLib.Layers.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.Layers.Crc32cLayer.html" + "relative_path": "api/LiteNetLib.Layers.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Layers", + "Summary": null }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.Layers.PacketLayerBase.yml", + "source_relative_path": "api/LiteNetLib.LiteConnectionRequest.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.Layers.PacketLayerBase.html" + "relative_path": "api/LiteNetLib.LiteConnectionRequest.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.LiteConnectionRequest", + "Summary": null }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.Layers.XorEncryptLayer.yml", + "source_relative_path": "api/LiteNetLib.LiteNetManager.NetPeerEnumerator-1.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.Layers.XorEncryptLayer.html" + "relative_path": "api/LiteNetLib.LiteNetManager.NetPeerEnumerator-1.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.LiteNetManager.NetPeerEnumerator", + "Summary": null }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.Layers.yml", + "source_relative_path": "api/LiteNetLib.LiteNetManager.NetPeerEnumerator.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.Layers.html" + "relative_path": "api/LiteNetLib.LiteNetManager.NetPeerEnumerator.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.LiteNetManager.NetPeerEnumerator", + "Summary": null + }, + { + "type": "ManagedReference", + "source_relative_path": "api/LiteNetLib.LiteNetManager.yml", + "output": { + ".html": { + "relative_path": "api/LiteNetLib.LiteNetManager.html" + } + }, + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.LiteNetManager", + "Summary": "

    Main class for all network operations. Can be used as client and/or server.

    \n" + }, + { + "type": "ManagedReference", + "source_relative_path": "api/LiteNetLib.LiteNetPeer.yml", + "output": { + ".html": { + "relative_path": "api/LiteNetLib.LiteNetPeer.html" + } + }, + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.LiteNetPeer", + "Summary": "

    Network peer. Main purpose is sending messages to specific peer.

    \n" }, { "type": "ManagedReference", @@ -201,7 +318,11 @@ "relative_path": "api/LiteNetLib.LocalAddrType.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.LocalAddrType", + "Summary": "

    Address type that you want to receive from NetUtils.GetLocalIp method

    \n" }, { "type": "ManagedReference", @@ -211,7 +332,11 @@ "relative_path": "api/LiteNetLib.NatAddressType.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NatAddressType", + "Summary": "

    Specifies the type of network address discovered during NAT punchthrough.

    \n" }, { "type": "ManagedReference", @@ -221,7 +346,25 @@ "relative_path": "api/LiteNetLib.NatPunchModule.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NatPunchModule", + "Summary": "

    Module for UDP NAT Hole punching operations. Can be accessed from NetManager

    \n" + }, + { + "type": "ManagedReference", + "source_relative_path": "api/LiteNetLib.NetConnectRequestPacket.yml", + "output": { + ".html": { + "relative_path": "api/LiteNetLib.NetConnectRequestPacket.html" + } + }, + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetConnectRequestPacket", + "Summary": null }, { "type": "ManagedReference", @@ -231,7 +374,11 @@ "relative_path": "api/LiteNetLib.NetConstants.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetConstants", + "Summary": "

    Network constants. Can be tuned from sources for your purposes.

    \n" }, { "type": "ManagedReference", @@ -241,27 +388,53 @@ "relative_path": "api/LiteNetLib.NetDebug.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetDebug", + "Summary": "

    Static class for defining your own LiteNetLib logger instead of Console.WriteLine\nor Debug.Log if compiled with UNITY flag

    \n" }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.NetLogLevel.yml", + "source_relative_path": "api/LiteNetLib.NetEvent.EType.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.NetLogLevel.html" + "relative_path": "api/LiteNetLib.NetEvent.EType.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetEvent.EType", + "Summary": "

    Specifies the category of the network event.

    \n" }, { "type": "ManagedReference", - "source_relative_path": "api/LiteNetLib.NetManager.NetPeerEnumerator.yml", + "source_relative_path": "api/LiteNetLib.NetEvent.yml", "output": { ".html": { - "relative_path": "api/LiteNetLib.NetManager.NetPeerEnumerator.html" + "relative_path": "api/LiteNetLib.NetEvent.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetEvent", + "Summary": "

    Internally used event type

    \n" + }, + { + "type": "ManagedReference", + "source_relative_path": "api/LiteNetLib.NetLogLevel.yml", + "output": { + ".html": { + "relative_path": "api/LiteNetLib.NetLogLevel.html" + } + }, + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetLogLevel", + "Summary": null }, { "type": "ManagedReference", @@ -271,7 +444,11 @@ "relative_path": "api/LiteNetLib.NetManager.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetManager", + "Summary": "

    More feature rich network manager with adjustable channels count

    \n" }, { "type": "ManagedReference", @@ -281,7 +458,11 @@ "relative_path": "api/LiteNetLib.NetPacketReader.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetPacketReader", + "Summary": null }, { "type": "ManagedReference", @@ -291,7 +472,11 @@ "relative_path": "api/LiteNetLib.NetPeer.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetPeer", + "Summary": "

    Improved LiteNetPeer with full multi-channel support

    \n" }, { "type": "ManagedReference", @@ -301,7 +486,11 @@ "relative_path": "api/LiteNetLib.NetStatistics.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetStatistics", + "Summary": "

    Thread-safe counter for network statistics including sent/received packets, bytes, and packet loss.

    \n" }, { "type": "ManagedReference", @@ -311,7 +500,11 @@ "relative_path": "api/LiteNetLib.NetUtils.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.NetUtils", + "Summary": "

    Some specific network utilities

    \n" }, { "type": "ManagedReference", @@ -321,7 +514,11 @@ "relative_path": "api/LiteNetLib.PooledPacket.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.PooledPacket", + "Summary": null }, { "type": "ManagedReference", @@ -331,7 +528,11 @@ "relative_path": "api/LiteNetLib.TooBigPacketException.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.TooBigPacketException", + "Summary": null }, { "type": "ManagedReference", @@ -341,7 +542,11 @@ "relative_path": "api/LiteNetLib.UnconnectedMessageType.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.UnconnectedMessageType", + "Summary": "

    Type of message that you receive in OnNetworkReceiveUnconnected event

    \n" }, { "type": "ManagedReference", @@ -351,7 +556,11 @@ "relative_path": "api/LiteNetLib.Utils.CRC32C.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.CRC32C", + "Summary": null }, { "type": "ManagedReference", @@ -361,7 +570,11 @@ "relative_path": "api/LiteNetLib.Utils.FastBitConverter.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.FastBitConverter", + "Summary": null }, { "type": "ManagedReference", @@ -371,7 +584,11 @@ "relative_path": "api/LiteNetLib.Utils.INetSerializable.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.INetSerializable", + "Summary": "

    Interface for implementing custom data serialization for network transmission.

    \n" }, { "type": "ManagedReference", @@ -381,7 +598,11 @@ "relative_path": "api/LiteNetLib.Utils.InvalidTypeException.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.InvalidTypeException", + "Summary": null }, { "type": "ManagedReference", @@ -391,7 +612,11 @@ "relative_path": "api/LiteNetLib.Utils.NetDataReader.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.NetDataReader", + "Summary": null }, { "type": "ManagedReference", @@ -401,7 +626,11 @@ "relative_path": "api/LiteNetLib.Utils.NetDataWriter.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.NetDataWriter", + "Summary": null }, { "type": "ManagedReference", @@ -411,7 +640,11 @@ "relative_path": "api/LiteNetLib.Utils.NetPacketProcessor.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.NetPacketProcessor", + "Summary": null }, { "type": "ManagedReference", @@ -421,7 +654,11 @@ "relative_path": "api/LiteNetLib.Utils.NetSerializer.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.NetSerializer", + "Summary": null }, { "type": "ManagedReference", @@ -431,7 +668,11 @@ "relative_path": "api/LiteNetLib.Utils.NtpLeapIndicator.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.NtpLeapIndicator", + "Summary": "

    Represents leap second warning from the server that instructs the client to add or remove leap second.

    \n" }, { "type": "ManagedReference", @@ -441,7 +682,11 @@ "relative_path": "api/LiteNetLib.Utils.NtpMode.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.NtpMode", + "Summary": "

    Describes SNTP packet mode, i.e. client or server.

    \n" }, { "type": "ManagedReference", @@ -451,7 +696,11 @@ "relative_path": "api/LiteNetLib.Utils.NtpPacket.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.NtpPacket", + "Summary": "

    Represents RFC4330 SNTP packet used for communication to and from a network time server.

    \n" }, { "type": "ManagedReference", @@ -461,7 +710,25 @@ "relative_path": "api/LiteNetLib.Utils.ParseException.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.ParseException", + "Summary": null + }, + { + "type": "ManagedReference", + "source_relative_path": "api/LiteNetLib.Utils.PreserveAttribute.yml", + "output": { + ".html": { + "relative_path": "api/LiteNetLib.Utils.PreserveAttribute.html" + } + }, + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils.PreserveAttribute", + "Summary": "

    PreserveAttribute prevents byte code stripping from removing a class, method, field, or property.

    \n" }, { "type": "ManagedReference", @@ -471,7 +738,11 @@ "relative_path": "api/LiteNetLib.Utils.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib.Utils", + "Summary": null }, { "type": "ManagedReference", @@ -481,7 +752,11 @@ "relative_path": "api/LiteNetLib.html" } }, - "version": "" + "version": "", + "Uid": null, + "IsMRef": true, + "Title": "LiteNetLib", + "Summary": null }, { "type": "Conceptual", @@ -499,6 +774,9 @@ "output": { ".html": { "relative_path": "api/toc.html" + }, + ".json": { + "relative_path": "api/toc.json" } }, "version": "" @@ -519,6 +797,9 @@ "output": { ".html": { "relative_path": "articles/toc.html" + }, + ".json": { + "relative_path": "articles/toc.json" } }, "version": "" @@ -539,12 +820,14 @@ "output": { ".html": { "relative_path": "toc.html" + }, + ".json": { + "relative_path": "toc.json" } }, "version": "" } ], - "version_info": {}, "groups": [ { "xrefmap": "xrefmap.yml" diff --git a/docs/search-stopwords.json b/docs/search-stopwords.json old mode 100644 new mode 100755 diff --git a/docs/styles/docfx.css b/docs/styles/docfx.css old mode 100644 new mode 100755 index 34ee31bb..7d80d139 --- a/docs/styles/docfx.css +++ b/docs/styles/docfx.css @@ -1,4 +1,7 @@ -/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */ +/** + * Licensed to the .NET Foundation under one or more agreements. + * The .NET Foundation licenses this file to you under the MIT license. + */ html, body { font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif; @@ -108,10 +111,6 @@ span.languagekeyword{ font-weight: bold; } -svg:hover path { - fill: #ffffff; -} - .hljs { display: inline; background-color: inherit; @@ -826,7 +825,7 @@ footer { .toc .level1 > li { display: block; } - + .toc .level1 > li:after { display: none; } @@ -1034,3 +1033,9 @@ div.embeddedvideo iframe { font-weight: bold; margin-top: 2em; } + +@media print { + @page { + margin: .4in; + } +} diff --git a/docs/styles/docfx.js b/docs/styles/docfx.js old mode 100644 new mode 100755 index 16bafe8c..39943509 --- a/docs/styles/docfx.js +++ b/docs/styles/docfx.js @@ -1,4 +1,5 @@ -// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. $(function () { var active = 'active'; var expanded = 'in'; @@ -127,13 +128,10 @@ $(function () { return; } try { - var worker = new Worker(relHref + 'styles/search-worker.min.js'); - if (!worker && !window.worker) { - localSearch(); - } else { - webWorkerSearch(); + if(!window.Worker){ + return; } - + webWorkerSearch(); renderSearchBox(); highlightKeywords(); addSearchEvent(); @@ -163,50 +161,14 @@ $(function () { } } - // Search factory - function localSearch() { - console.log("using local search"); - var lunrIndex = lunr(function () { - this.ref('href'); - this.field('title', { boost: 50 }); - this.field('keywords', { boost: 20 }); - }); - lunr.tokenizer.seperator = /[\s\-\.]+/; - var searchData = {}; - var searchDataRequest = new XMLHttpRequest(); - - var indexPath = relHref + "index.json"; - if (indexPath) { - searchDataRequest.open('GET', indexPath); - searchDataRequest.onload = function () { - if (this.status != 200) { - return; - } - searchData = JSON.parse(this.responseText); - for (var prop in searchData) { - if (searchData.hasOwnProperty(prop)) { - lunrIndex.add(searchData[prop]); - } - } - } - searchDataRequest.send(); - } - - $("body").bind("queryReady", function () { - var hits = lunrIndex.search(query); - var results = []; - hits.forEach(function (hit) { - var item = searchData[hit.ref]; - results.push({ 'href': item.href, 'title': item.title, 'keywords': item.keywords }); - }); - handleSearchResults(results); - }); - } - function webWorkerSearch() { - console.log("using Web Worker"); var indexReady = $.Deferred(); + var worker = new Worker(relHref + 'styles/search-worker.min.js'); + worker.onerror = function (oEvent) { + console.error('Error occurred at search-worker. message: ' + oEvent.message); + } + worker.onmessage = function (oEvent) { switch (oEvent.data.e) { case 'index-ready': @@ -251,7 +213,7 @@ $(function () { $('#search-query').keyup(function () { query = $(this).val(); - if (query.length < 3) { + if (query === '') { flipContents("show"); } else { flipContents("hide"); @@ -288,6 +250,9 @@ $(function () { } function extractContentBrief(content) { + if (!content) { + return + } var briefOffset = 512; var words = query.split(/\s+/g); var queryIndex = content.indexOf(words[0]); @@ -306,7 +271,7 @@ $(function () { pagination.removeData("twbs-pagination"); if (hits.length === 0) { $('#search-results>.sr-items').html('

    No results found

    '); - } else { + } else { pagination.twbsPagination({ first: pagination.data('first'), prev: pagination.data('prev'), @@ -323,7 +288,7 @@ $(function () { var itemRawHref = relativeUrlToAbsoluteUrl(currentUrl, relHref + hit.href); var itemHref = relHref + hit.href + "?q=" + query; var itemTitle = hit.title; - var itemBrief = extractContentBrief(hit.keywords); + var itemBrief = extractContentBrief(hit.summary || ''); var itemNode = $('
    ').attr('class', 'sr-item'); var itemTitleNode = $('
    ').attr('class', 'item-title').append($('').attr('href', itemHref).attr("target", "_blank").attr("rel", "noopener noreferrer").text(itemTitle)); @@ -354,7 +319,7 @@ $(function () { renderBreadcrumb(); showSearch(); } - + function showSearch() { if ($('#search-results').length !== 0) { $('#search').show(); @@ -443,7 +408,7 @@ $(function () { function registerTocEvents() { var tocFilterInput = $('#toc_filter_input'); var tocFilterClearButton = $('#toc_filter_clear'); - + $('.toc .nav > li > .expand-stub').click(function (e) { $(e.target).parent().toggleClass(expanded); }); @@ -477,7 +442,7 @@ $(function () { parent.removeClass(show); parent.removeClass(filtered); }) - + // Get leaf nodes $('#toc li>a').filter(function (i, e) { return $(e).siblings().length === 0 @@ -518,7 +483,7 @@ $(function () { return false; } }); - + // toc filter clear button tocFilterClearButton.hide(); tocFilterClearButton.on("click", function(e){ diff --git a/docs/styles/docfx.vendor.min.css b/docs/styles/docfx.vendor.min.css old mode 100644 new mode 100755 index b809553e..c19c2f98 --- a/docs/styles/docfx.vendor.min.css +++ b/docs/styles/docfx.vendor.min.css @@ -1,4 +1,4 @@ -html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*,*:before,*:after{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:Glyphicons Halflings;src:url(./glyphicons-halflings-regular-PIHUWCJO.eot);src:url(./glyphicons-halflings-regular-PIHUWCJO.eot?#iefix) format("embedded-opentype"),url(./glyphicons-halflings-regular-W4DYDFZM.woff2) format("woff2"),url(./glyphicons-halflings-regular-JOUF32XT.woff) format("woff"),url(./glyphicons-halflings-regular-ACNUA6UY.ttf) format("truetype"),url(./glyphicons-halflings-regular-QXYEM3FU.svg#glyphicons_halflingsregular) format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:Glyphicons Halflings;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"*"}.glyphicon-plus:before{content:"+"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\a5"}.glyphicon-jpy:before{content:"\a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width: 768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover,a.text-primary:focus{color:#286090}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover,a.bg-primary:focus{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:"\2014\a0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eeeeee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:"\a0\2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Courier New,monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px #00000040}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*=col-]{padding-right:0;padding-left:0}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width: 768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width: 992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width: 1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \ ;line-height:normal}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px #00000013,0 0 8px #66afe999}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio: 0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month]{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \ ;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px #00000013,0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px #00000013,0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px #00000013,0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width: 768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px #00000020}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:focus,.btn-default.focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:focus,.btn-primary.focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;background-image:none;border-color:#204d74}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \ ;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px #0000002d}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;inset:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid \ }.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px #00000020}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 4px 4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=radio],[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width: 768px){.navbar{border-radius:4px}}@media (min-width: 768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px #ffffff1a;-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width: 768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width: 768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width: 768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width: 768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width: 767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width: 768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin:8px -15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px #ffffff1a,0 1px #ffffff1a}@media (min-width: 768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width: 767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:4px 4px 0 0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width: 768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width: 767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px #0000001a}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px #00000026;-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#c7ddef}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px #0000000d}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px #0000000d}.well blockquote{border-color:#ddd;border-color:#00000026}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;inset:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translateY(-25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px #00000080;outline:0}.modal-backdrop{position:fixed;inset:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px #00000080}.modal-sm{width:300px}}@media (min-width: 992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px #0003}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:#00000040;border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:#00000040;border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:#00000040}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:#00000040}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0,0,0);transform:translateZ(0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:#0000;filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#80000000",endColorstr="#00000000",GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#00000000",endColorstr="#80000000",GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \ ;background-color:#0000;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width: 768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width: 767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width: 767px){.visible-xs-block{display:block!important}}@media (max-width: 767px){.visible-xs-inline{display:inline!important}}@media (max-width: 767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-block{display:block!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline{display:inline!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-block{display:block!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline{display:inline!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width: 1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width: 1200px){.visible-lg-block{display:block!important}}@media (min-width: 1200px){.visible-lg-inline{display:inline!important}}@media (min-width: 1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width: 767px){.hidden-xs{display:none!important}}@media (min-width: 768px) and (max-width: 991px){.hidden-sm{display:none!important}}@media (min-width: 992px) and (max-width: 1199px){.hidden-md{display:none!important}}@media (min-width: 1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*,*:before,*:after{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:Glyphicons Halflings;src:url("./glyphicons-halflings-regular-PIHUWCJO.eot");src:url("./glyphicons-halflings-regular-PIHUWCJO.eot?#iefix") format("embedded-opentype"),url("./glyphicons-halflings-regular-W4DYDFZM.woff2") format("woff2"),url("./glyphicons-halflings-regular-JOUF32XT.woff") format("woff"),url("./glyphicons-halflings-regular-ACNUA6UY.ttf") format("truetype"),url("./glyphicons-halflings-regular-QXYEM3FU.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:Glyphicons Halflings;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"*"}.glyphicon-plus:before{content:"+"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\a5"}.glyphicon-jpy:before{content:"\a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width: 768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover,a.text-primary:focus{color:#286090}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover,a.bg-primary:focus{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:"\2014\a0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eeeeee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:"\a0\2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Courier New,monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px #00000040}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*=col-]{padding-right:0;padding-left:0}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width: 768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width: 992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width: 1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \ ;line-height:normal}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px #00000013,0 0 8px #66afe999}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio: 0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month]{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \ ;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px #00000013,0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px #00000013,0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px #00000013}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px #00000013,0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width: 768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px #00000020}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:focus,.btn-default.focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:focus,.btn-primary.focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;background-image:none;border-color:#204d74}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \ ;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px #0000002d}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;inset:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid \ }.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px #00000020}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 4px 4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=radio],[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width: 768px){.navbar{border-radius:4px}}@media (min-width: 768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px #ffffff1a;-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width: 768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width: 768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width: 768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width: 768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width: 767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width: 768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin:8px -15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px #ffffff1a,0 1px #ffffff1a}@media (min-width: 768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width: 767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:4px 4px 0 0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width: 768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width: 767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px #0000001a}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px #00000026;-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#c7ddef}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px #0000000d}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px #0000000d}.well blockquote{border-color:#ddd;border-color:#00000026}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;inset:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translateY(-25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px #00000080;outline:0}.modal-backdrop{position:fixed;inset:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px #00000080}.modal-sm{width:300px}}@media (min-width: 992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px #0003}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:#00000040;border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:#00000040;border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:#00000040}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:#00000040}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0,0,0);transform:translateZ(0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:#0000;filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,#00000080,#0000);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#80000000",endColorstr="#00000000",GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,#0000,#00000080);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#00000000",endColorstr="#80000000",GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \ ;background-color:#0000;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width: 768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width: 767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width: 767px){.visible-xs-block{display:block!important}}@media (max-width: 767px){.visible-xs-inline{display:inline!important}}@media (max-width: 767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-block{display:block!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline{display:inline!important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-block{display:block!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline{display:inline!important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width: 1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width: 1200px){.visible-lg-block{display:block!important}}@media (min-width: 1200px){.visible-lg-inline{display:inline!important}}@media (min-width: 1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width: 767px){.hidden-xs{display:none!important}}@media (min-width: 768px) and (max-width: 991px){.hidden-sm{display:none!important}}@media (min-width: 992px) and (max-width: 1199px){.hidden-md{display:none!important}}@media (min-width: 1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} /*! Bundled license information: @default/bootstrap/dist/css/bootstrap.css: diff --git a/docs/styles/docfx.vendor.min.css.map b/docs/styles/docfx.vendor.min.css.map old mode 100644 new mode 100755 index 31cf61ec..abba9403 --- a/docs/styles/docfx.vendor.min.css.map +++ b/docs/styles/docfx.vendor.min.css.map @@ -2,6 +2,6 @@ "version": 3, "sources": ["../../node_modules/@default/bootstrap/dist/css/bootstrap.css", "../../node_modules/@default/bootstrap/dist/css/less/normalize.less", "../../node_modules/@default/bootstrap/dist/css/less/print.less", "../../node_modules/@default/bootstrap/dist/css/less/glyphicons.less", "../../node_modules/@default/bootstrap/dist/css/less/scaffolding.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/vendor-prefixes.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/tab-focus.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/image.less", "../../node_modules/@default/bootstrap/dist/css/less/type.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/text-emphasis.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/background-variant.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/text-overflow.less", "../../node_modules/@default/bootstrap/dist/css/less/code.less", "../../node_modules/@default/bootstrap/dist/css/less/grid.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/grid.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/grid-framework.less", "../../node_modules/@default/bootstrap/dist/css/less/tables.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/table-row.less", "../../node_modules/@default/bootstrap/dist/css/less/forms.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/forms.less", "../../node_modules/@default/bootstrap/dist/css/less/buttons.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/buttons.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/opacity.less", "../../node_modules/@default/bootstrap/dist/css/less/component-animations.less", "../../node_modules/@default/bootstrap/dist/css/less/dropdowns.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/nav-divider.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/reset-filter.less", "../../node_modules/@default/bootstrap/dist/css/less/button-groups.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/border-radius.less", "../../node_modules/@default/bootstrap/dist/css/less/input-groups.less", "../../node_modules/@default/bootstrap/dist/css/less/navs.less", "../../node_modules/@default/bootstrap/dist/css/less/navbar.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/nav-vertical-align.less", "../../node_modules/@default/bootstrap/dist/css/less/utilities.less", "../../node_modules/@default/bootstrap/dist/css/less/breadcrumbs.less", "../../node_modules/@default/bootstrap/dist/css/less/pagination.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/pagination.less", "../../node_modules/@default/bootstrap/dist/css/less/pager.less", "../../node_modules/@default/bootstrap/dist/css/less/labels.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/labels.less", "../../node_modules/@default/bootstrap/dist/css/less/badges.less", "../../node_modules/@default/bootstrap/dist/css/less/jumbotron.less", "../../node_modules/@default/bootstrap/dist/css/less/thumbnails.less", "../../node_modules/@default/bootstrap/dist/css/less/alerts.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/alerts.less", "../../node_modules/@default/bootstrap/dist/css/less/progress-bars.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/gradients.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/progress-bar.less", "../../node_modules/@default/bootstrap/dist/css/less/media.less", "../../node_modules/@default/bootstrap/dist/css/less/list-group.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/list-group.less", "../../node_modules/@default/bootstrap/dist/css/less/panels.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/panels.less", "../../node_modules/@default/bootstrap/dist/css/less/responsive-embed.less", "../../node_modules/@default/bootstrap/dist/css/less/wells.less", "../../node_modules/@default/bootstrap/dist/css/less/close.less", "../../node_modules/@default/bootstrap/dist/css/less/modals.less", "../../node_modules/@default/bootstrap/dist/css/less/tooltip.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/reset-text.less", "../../node_modules/@default/bootstrap/dist/css/less/popovers.less", "../../node_modules/@default/bootstrap/dist/css/less/carousel.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/clearfix.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/center-block.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/hide-text.less", "../../node_modules/@default/bootstrap/dist/css/less/responsive-utilities.less", "../../node_modules/@default/bootstrap/dist/css/less/mixins/responsive-visibility.less", "../../node_modules/@default/highlight.js/styles/github.css"], "sourcesContent": ["/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: none;\n text-decoration: underline;\n text-decoration: underline dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n color: #000 !important;\n text-shadow: none !important;\n background: transparent !important;\n box-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: \"Glyphicons Halflings\";\n src: url(\"../fonts/glyphicons-halflings-regular.eot\");\n src: url(\"../fonts/glyphicons-halflings-regular.eot?#iefix\") format(\"embedded-opentype\"), url(\"../fonts/glyphicons-halflings-regular.woff2\") format(\"woff2\"), url(\"../fonts/glyphicons-halflings-regular.woff\") format(\"woff\"), url(\"../fonts/glyphicons-halflings-regular.ttf\") format(\"truetype\"), url(\"../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular\") format(\"svg\");\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: \"Glyphicons Halflings\";\n font-style: normal;\n font-weight: 400;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: 400;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-right: 5px;\n padding-left: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: 700;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: \"\\2014 \\00A0\";\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n text-align: right;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: \"\";\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: \"\\00A0 \\2014\";\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n color: #333333;\n word-break: break-all;\n word-wrap: break-word;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n.row {\n margin-right: -15px;\n margin-left: -15px;\n}\n.row-no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n.row-no-gutters [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n.col-xs-1,\n.col-sm-1,\n.col-md-1,\n.col-lg-1,\n.col-xs-2,\n.col-sm-2,\n.col-md-2,\n.col-lg-2,\n.col-xs-3,\n.col-sm-3,\n.col-md-3,\n.col-lg-3,\n.col-xs-4,\n.col-sm-4,\n.col-md-4,\n.col-lg-4,\n.col-xs-5,\n.col-sm-5,\n.col-md-5,\n.col-lg-5,\n.col-xs-6,\n.col-sm-6,\n.col-md-6,\n.col-lg-6,\n.col-xs-7,\n.col-sm-7,\n.col-md-7,\n.col-lg-7,\n.col-xs-8,\n.col-sm-8,\n.col-md-8,\n.col-lg-8,\n.col-xs-9,\n.col-sm-9,\n.col-md-9,\n.col-lg-9,\n.col-xs-10,\n.col-sm-10,\n.col-md-10,\n.col-lg-10,\n.col-xs-11,\n.col-sm-11,\n.col-md-11,\n.col-lg-11,\n.col-xs-12,\n.col-sm-12,\n.col-md-12,\n.col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-right: 15px;\n padding-left: 15px;\n}\n.col-xs-1,\n.col-xs-2,\n.col-xs-3,\n.col-xs-4,\n.col-xs-5,\n.col-xs-6,\n.col-xs-7,\n.col-xs-8,\n.col-xs-9,\n.col-xs-10,\n.col-xs-11,\n.col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1,\n .col-sm-2,\n .col-sm-3,\n .col-sm-4,\n .col-sm-5,\n .col-sm-6,\n .col-sm-7,\n .col-sm-8,\n .col-sm-9,\n .col-sm-10,\n .col-sm-11,\n .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1,\n .col-md-2,\n .col-md-3,\n .col-md-4,\n .col-md-5,\n .col-md-6,\n .col-md-7,\n .col-md-8,\n .col-md-9,\n .col-md-10,\n .col-md-11,\n .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1,\n .col-lg-2,\n .col-lg-3,\n .col-lg-4,\n .col-lg-5,\n .col-lg-6,\n .col-lg-7,\n .col-lg-8,\n .col-lg-9,\n .col-lg-10,\n .col-lg-11,\n .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ntable col[class*=\"col-\"] {\n position: static;\n display: table-column;\n float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n display: table-cell;\n float: none;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n min-height: 0.01%;\n overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: 700;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n -webkit-appearance: none;\n appearance: none;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: 400;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-top: 4px \\9;\n margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: 400;\n vertical-align: middle;\n cursor: pointer;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\n.form-control-static {\n min-height: 34px;\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-right: 0;\n padding-left: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n background-color: #f2dede;\n border-color: #a94442;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n padding-top: 7px;\n margin-top: 0;\n margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n padding-top: 7px;\n margin-bottom: 0;\n text-align: right;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n outline: 0;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n filter: alpha(opacity=65);\n opacity: 0.65;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n background-image: none;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n background-image: none;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n background-image: none;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n background-image: none;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n background-image: none;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n background-image: none;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n font-weight: 400;\n color: #337ab7;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n font-size: 14px;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: 400;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n color: #262626;\n text-decoration: none;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n background-color: #337ab7;\n outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n cursor: not-allowed;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n content: \"\";\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n right: 0;\n left: auto;\n }\n .navbar-right .dropdown-menu-left {\n right: auto;\n left: 0;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-right: 8px;\n padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-right: 12px;\n padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n display: table-cell;\n float: none;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-right: 0;\n padding-left: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: 400;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n cursor: not-allowed;\n background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n cursor: default;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n margin-bottom: 5px;\n text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n margin-bottom: 5px;\n text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n padding-right: 15px;\n padding-left: 15px;\n overflow-x: visible;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-right: 0;\n padding-left: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-brand {\n float: left;\n height: 50px;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n padding: 9px 10px;\n margin-right: 15px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n padding: 10px 15px;\n margin-right: -15px;\n margin-left: -15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n padding-top: 0;\n padding-bottom: 0;\n margin-right: 0;\n margin-left: 0;\n border: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-right: 15px;\n margin-left: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n color: #fff;\n background-color: #080808;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n padding: 0 5px;\n color: #ccc;\n content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n margin-left: -1px;\n line-height: 1.42857143;\n color: #337ab7;\n text-decoration: none;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eeeeee;\n border-color: #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-top-left-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n cursor: default;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n cursor: not-allowed;\n background-color: #fff;\n border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-top-left-radius: 6px;\n border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-top-left-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-top-right-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n text-align: center;\n list-style: none;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n cursor: not-allowed;\n background-color: #fff;\n}\n.label {\n display: inline;\n padding: 0.2em 0.6em 0.3em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n padding-right: 15px;\n padding-left: 15px;\n border-radius: 6px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-right: 60px;\n padding-left: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-right: auto;\n margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n height: 20px;\n margin-bottom: 20px;\n overflow: hidden;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n overflow: hidden;\n zoom: 1;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n padding-left: 0;\n margin-bottom: 20px;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n color: #777777;\n cursor: not-allowed;\n background-color: #eeeeee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n color: #555;\n text-decoration: none;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-right: 15px;\n padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n margin-bottom: 0;\n border: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n filter: alpha(opacity=20);\n opacity: 0.2;\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n filter: alpha(opacity=50);\n opacity: 0.5;\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n display: none;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n filter: alpha(opacity=0);\n opacity: 0;\n}\n.modal-backdrop.in {\n filter: alpha(opacity=50);\n opacity: 0.5;\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-bottom: 0;\n margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: 400;\n line-height: 1.42857143;\n line-break: auto;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n white-space: normal;\n font-size: 12px;\n filter: alpha(opacity=0);\n opacity: 0;\n}\n.tooltip.in {\n filter: alpha(opacity=90);\n opacity: 0.9;\n}\n.tooltip.top {\n padding: 5px 0;\n margin-top: -3px;\n}\n.tooltip.right {\n padding: 0 5px;\n margin-left: 3px;\n}\n.tooltip.bottom {\n padding: 5px 0;\n margin-top: 3px;\n}\n.tooltip.left {\n padding: 0 5px;\n margin-left: -3px;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n right: 5px;\n bottom: 0;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: 400;\n line-height: 1.42857143;\n line-break: auto;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n white-space: normal;\n font-size: 14px;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow:after {\n content: \"\";\n border-width: 10px;\n}\n.popover.top > .arrow {\n bottom: -11px;\n left: 50%;\n margin-left: -11px;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n bottom: 1px;\n margin-left: -10px;\n content: \" \";\n border-top-color: #fff;\n border-bottom-width: 0;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n border-left-width: 0;\n}\n.popover.right > .arrow:after {\n bottom: -10px;\n left: 1px;\n content: \" \";\n border-right-color: #fff;\n border-left-width: 0;\n}\n.popover.bottom > .arrow {\n top: -11px;\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n.popover.bottom > .arrow:after {\n top: 1px;\n margin-left: -10px;\n content: \" \";\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n right: 1px;\n bottom: -10px;\n content: \" \";\n border-right-width: 0;\n border-left-color: #fff;\n}\n.popover-title {\n padding: 8px 14px;\n margin: 0;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n.carousel-inner > .item {\n position: relative;\n display: none;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n -moz-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 15%;\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n background-color: rgba(0, 0, 0, 0);\n filter: alpha(opacity=50);\n opacity: 0.5;\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n background-repeat: repeat-x;\n}\n.carousel-control.right {\n right: 0;\n left: auto;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n filter: alpha(opacity=90);\n opacity: 0.9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n margin-top: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n font-family: serif;\n line-height: 1;\n}\n.carousel-control .icon-prev:before {\n content: \"\\2039\";\n}\n.carousel-control .icon-next:before {\n content: \"\\203a\";\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n padding-left: 0;\n margin-left: -30%;\n text-align: center;\n list-style: none;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n border: 1px solid #fff;\n border-radius: 10px;\n}\n.carousel-indicators .active {\n width: 12px;\n height: 12px;\n margin: 0;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n right: 20%;\n left: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n display: table;\n content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-right: auto;\n margin-left: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */", "// stylelint-disable\n\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS and IE text size adjust after device orientation change,\n// without disabling user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability of focused elements when they are also in an\n// active/hover state.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// 1. Remove the bottom border in Chrome 57- and Firefox 39-.\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n//\n\nabbr[title] {\n border-bottom: none; // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n box-sizing: content-box; //2\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n", "// stylelint-disable declaration-no-important, selector-no-qualifying-type\n\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n color: #000 !important; // Black prints faster: h5bp.com/s\n text-shadow: none !important;\n background: transparent !important;\n box-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n}\n", "// stylelint-disable value-list-comma-newline-after, value-list-comma-space-after, indentation, declaration-colon-newline-after, font-family-no-missing-generic-family-keyword\n\n//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: \"Glyphicons Halflings\";\n src: url(\"@{icon-font-path}@{icon-font-name}.eot\");\n src: url(\"@{icon-font-path}@{icon-font-name}.eot?#iefix\") format(\"embedded-opentype\"),\n url(\"@{icon-font-path}@{icon-font-name}.woff2\") format(\"woff2\"),\n url(\"@{icon-font-path}@{icon-font-name}.woff\") format(\"woff\"),\n url(\"@{icon-font-path}@{icon-font-name}.ttf\") format(\"truetype\"),\n url(\"@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}\") format(\"svg\");\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: \"Glyphicons Halflings\";\n font-style: normal;\n font-weight: 400;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\002a\"; } }\n.glyphicon-plus { &:before { content: \"\\002b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n", "//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// https://getbootstrap.com/docs/3.4/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: https://a11yproject.com/posts/how-to-hide-content\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n[role=\"button\"] {\n cursor: pointer;\n}\n", "// stylelint-disable indentation, property-no-vendor-prefix, selector-no-vendor-prefix\n\n// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n word-wrap: break-word;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n", "// WebKit-style focus\n\n.tab-focus() {\n // WebKit-specific. Other browsers will keep their default outline style.\n // (Initially tried to also force default via `outline: initial`,\n // but that seems to erroneously remove the outline in Firefox altogether.)\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n", "// stylelint-disable media-feature-name-no-vendor-prefix, media-feature-parentheses-space-inside, media-feature-name-no-unknown, indentation, at-rule-name-space-after\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n", "// stylelint-disable selector-list-comma-newline-after, selector-no-qualifying-type\n\n//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: 400;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n padding: .2em;\n background-color: @state-warning-bg;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-right: 5px;\n padding-left: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: 700;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @dl-horizontal-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n}\n\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: \"\\2014 \\00A0\"; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n text-align: right;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: \"\"; }\n &:after {\n content: \"\\00A0 \\2014\"; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n", "// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover,\n a&:focus {\n color: darken(@color, 10%);\n }\n}\n", "// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover,\n a&:focus {\n background-color: darken(@color, 10%);\n }\n}\n", "// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n", "//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n color: @pre-color;\n word-break: break-all;\n word-wrap: break-word;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n", "//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n.row-no-gutters {\n margin-right: 0;\n margin-left: 0;\n\n [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n", "// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n padding-right: ceil((@gutter / 2));\n padding-left: floor((@gutter / 2));\n margin-right: auto;\n margin-left: auto;\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-right: floor((@gutter / -2));\n margin-left: ceil((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n", "// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-right: floor((@grid-gutter-width / 2));\n padding-left: ceil((@grid-gutter-width / 2));\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n", "// stylelint-disable selector-max-type, selector-max-compound-selectors, selector-no-qualifying-type\n\n//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n\n // Table cell sizing\n //\n // Reset default table behavior\n\n col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n display: table-column;\n float: none;\n }\n\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n display: table-cell;\n float: none;\n }\n }\n}\n\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\n\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n min-height: .01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n overflow-x: auto;\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * .75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n", "// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n", "// stylelint-disable selector-no-qualifying-type, property-no-vendor-prefix, media-feature-name-no-vendor-prefix\n\n//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: 700;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\ninput[type=\"search\"] {\n // Override content-box in Normalize (* isn't specific enough)\n .box-sizing(border-box);\n\n // Search inputs in iOS\n //\n // This overrides the extra rounded corners on search inputs in iOS so that our\n // `.form-control` class can properly style them. Note that this cannot simply\n // be added to `.form-control` as it's not specific enough. For details, see\n // https://github.com/twbs/bootstrap/issues/11586.\n -webkit-appearance: none;\n appearance: none;\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n\n // Apply same disabled cursor tweak as for inputs\n // Some special care is needed because