diff --git a/Basis Server/BasisNetworkClient/BasisNetworkClient.cs b/Basis Server/BasisNetworkClient/BasisNetworkClient.cs index 753b9203a..f0a21dd27 100644 --- a/Basis Server/BasisNetworkClient/BasisNetworkClient.cs +++ b/Basis Server/BasisNetworkClient/BasisNetworkClient.cs @@ -1,4 +1,5 @@ using Basis.Network.Core; +using BasisNetworkCore; using LiteNetLib; using LiteNetLib.Utils; using static Basis.Network.Core.Serializable.SerializableBasis; @@ -37,12 +38,13 @@ public static NetPeer StartClient(string IP, int port, ReadyMessage ReadyMessage UnsyncedEvents = true, }; client.Start(); - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); //this is the only time we dont put key! Writer.Put(BasisNetworkVersion.ServerVersion); AuthenticationMessage.Serialize(Writer); ReadyMessage.Serialize(Writer); peer = client.Connect(IP, port, Writer); + NetDataWriterPool.ReturnWriter(Writer); return peer; } else diff --git a/Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs b/Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs index f526b6ffa..e87e547ec 100644 --- a/Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs +++ b/Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs @@ -1,21 +1,28 @@ +using Basis.Network.Core; +using Basis.Scripts.Networking.Compression; +using BasisNetworkCore; +using LiteNetLib; +using LiteNetLib.Utils; +using System.Reflection.PortableExecutable; +using System.Runtime.Intrinsics.X86; using System.Text; using static Basis.Network.Core.Serializable.SerializableBasis; +using static BasisNetworkPrimitiveCompression; using static SerializableBasis; namespace Basis { class Program { + public static string Password = "default_password"; + private static readonly object nameLock = new object(); // To synchronize name generation + public static NetPeer LocalPLayer; public static void Main(string[] args) { // Set up global exception handlers AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; - // Get the path to the config.xml file in the application's directory - string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.xml"); - - // Create a cancellation token source var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; @@ -25,32 +32,37 @@ public static void Main(string[] args) { try { + // Generate random UUID and player name + string randomUUID = GenerateFakeUUID(); + string randomPlayerName = GenerateRandomPlayerName(); + ReadyMessage RM = new ReadyMessage { playerMetaDataMessage = new PlayerMetaDataMessage() }; - RM.playerMetaDataMessage.playerDisplayName = "Fake User"; - RM.playerMetaDataMessage.playerUUID = "UUID Test"; + RM.playerMetaDataMessage.playerDisplayName = randomPlayerName; + RM.playerMetaDataMessage.playerUUID = randomUUID; RM.clientAvatarChangeMessage = new ClientAvatarChangeMessage { - byteArray = new byte[13] + byteArray = new byte[0], + loadMode = 2 }; RM.localAvatarSyncMessage = new LocalAvatarSyncMessage { - array = new byte[LocalAvatarSyncMessage.AvatarSyncSize] + array = AvatarMessage, }; AuthenticationMessage Authmessage = new AuthenticationMessage { - bytes = Encoding.UTF8.GetBytes("default_password") + bytes = Encoding.UTF8.GetBytes(Password) }; BasisNetworkClient.AuthenticationMessage = Authmessage; - BasisNetworkClient.StartClient("localhost", 4296, RM,true); - BNL.Log("Connecting!"); + LocalPLayer = BasisNetworkClient.StartClient("localhost", 4296, RM, true); + BasisNetworkClient.listener.NetworkReceiveEvent += NetworkReceiveEvent; + BNL.Log($"Connecting! Player Name: {randomPlayerName}, UUID: {randomUUID}"); } catch (Exception ex) { BNL.LogError($"Server encountered an error: {ex.Message} {ex.StackTrace}"); - // Optionally, handle server restart or log critical errors } }, cancellationToken); @@ -72,14 +84,104 @@ public static void Main(string[] args) } BNL.Log("Server shut down successfully."); }; - + ushort[] UshortArray = new ushort[LocalAvatarSyncMessage.StoredBones]; // Keep the application running while (true) { - Thread.Sleep(15000); + // SendMovement(); + Thread.Sleep(33); + } + } + + private static void NetworkReceiveEvent(NetPeer peer, NetPacketReader Reader, byte channel, DeliveryMethod deliveryMethod) + { + + //loop back index 0 (0 being real player) + if (peer.Id == 0) + { + if (BasisNetworkCommons.MovementChannel == channel) + { + ServerSideSyncPlayerMessage SSM = new ServerSideSyncPlayerMessage(); + SSM.Deserialize(Reader); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); + SSM.avatarSerialization.Serialize(Writer); + LocalPLayer.Send(Writer, BasisNetworkCommons.MovementChannel, deliveryMethod); + NetDataWriterPool.ReturnWriter(Writer); + } + else + { + if(BasisNetworkCommons.FallChannel == channel) + { + if (deliveryMethod == DeliveryMethod.Unreliable) + { + if (Reader.TryGetByte(out byte Byte)) + { + NetworkReceiveEvent(peer, Reader, Byte, deliveryMethod); + } + else + { + BNL.LogError($"Unknown channel no data remains: {channel} " + Reader.AvailableBytes); + Reader.Recycle(); + } + } + else + { + BNL.LogError($"Unknown channel: {channel} " + Reader.AvailableBytes); + Reader.Recycle(); + } + } + } + + } + } + + public static byte[] AvatarMessage = new byte[LocalAvatarSyncMessage.AvatarSyncSize]; + public static Vector3 Position = new Vector3(0,0,0); + public static Quaternion Rotation = new Quaternion(0,0,0,1); + public static float[] FloatArray = new float[LocalAvatarSyncMessage.StoredBones]; + public static ushort[] UshortArray = new ushort[LocalAvatarSyncMessage.StoredBones]; + public static void SendMovement() + { + if (LocalPLayer != null) + { + int Offset = 0; + WriteVectorFloatToBytes(Position, ref AvatarMessage, ref Offset); + WriteQuaternionToBytes(Rotation, ref AvatarMessage, ref Offset, RotationCompression); + WriteUShortsToBytes(UshortArray, ref AvatarMessage, ref Offset); + LocalPLayer.Send(AvatarMessage, BasisNetworkCommons.MovementChannel, DeliveryMethod.Sequenced); } } + public static ushort Compress(float value, float MinValue, float MaxValue, float valueDiffence) + { + // Clamp the value to ensure it's within the specified range + value = Math.Clamp(value, MinValue, MaxValue); + + // Map the float value to the ushort range + float normalized = (value - MinValue) / (valueDiffence); // 0..1 + return (ushort)(normalized * ushortRangeDifference);//+ UShortMin (its always zero) + } + private const ushort UShortMin = ushort.MinValue; // 0 + private const ushort UShortMax = ushort.MaxValue; // 65535 + private const ushort ushortRangeDifference = UShortMax - UShortMin; + public static BasisRangedUshortFloatData RotationCompression = new BasisRangedUshortFloatData(-1f, 1f, 0.001f); + public static void WriteUShortsToBytes(ushort[] values, ref byte[] bytes, ref int offset) + { + EnsureSize(ref bytes, offset + LengthUshortBytes); + // Manually copy ushort values as bytes + for (int index = 0; index < LocalAvatarSyncMessage.StoredBones; index++) + { + WriteUShortToBytes(values[index], ref bytes, ref offset); + } + } + // Manual ushort to bytes conversion (without BitConverter) + private unsafe static void WriteUShortToBytes(ushort value, ref byte[] bytes, ref int offset) + { + // Manually write the bytes + bytes[offset] = (byte)(value & 0xFF); + bytes[offset + 1] = (byte)((value >> 8) & 0xFF); + offset += 2; + } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { var exception = e.ExceptionObject as Exception; @@ -103,5 +205,138 @@ private static void TaskScheduler_UnobservedTaskException(object sender, Unobser } e.SetObserved(); // Prevents the application from crashing } + + private static string GenerateFakeUUID() + { + // Generate a fake UUID-like string + Guid guid = Guid.NewGuid(); + return guid.ToString(); + } + + private static string GenerateRandomPlayerName() + { + // Thread-safe unique player name generation + lock (nameLock) + { + string[] adjectives = { "Swift", "Brave", "Clever", "Fierce", "Nimble", "Silent", "Bold", "Lucky", "Strong", "Mighty", "Sneaky", "Fearless", "Wise", "Vicious", "Daring" }; + string[] nouns = { "Warrior", "Hunter", "Mage", "Rogue", "Paladin", "Shaman", "Knight", "Archer", "Monk", "Druid", "Assassin", "Sorcerer", "Ranger", "Guardian", "Berserker" }; + string[] titles = { "the Swift", "the Bold", "the Silent", "the Brave", "the Fierce", "the Wise", "the Protector", "the Shadow", "the Flame", "the Phantom" }; + + // Colors with their corresponding names and hex codes for Unity's Rich Text + (string Name, string Hex)[] colors = + { + ("Red", "#FF0000"), + ("Blue", "#0000FF"), + ("Green", "#008000"), + ("Yellow", "#FFFF00"), + ("Black", "#000000"), + ("White", "#FFFFFF"), + ("Silver", "#C0C0C0"), + ("Golden", "#FFD700"), + ("Crimson", "#DC143C"), + ("Azure", "#007FFF"), + ("Emerald", "#50C878"), + ("Amber", "#FFBF00") + }; + + string[] animals = { "Wolf", "Tiger", "Eagle", "Dragon", "Lion", "Bear", "Hawk", "Panther", "Raven", "Serpent", "Fox", "Falcon" }; + + Random random = new Random(); + + // Randomly select one element from each array + string adjective = adjectives[random.Next(adjectives.Length)]; + string noun = nouns[random.Next(nouns.Length)]; + string title = titles[random.Next(titles.Length)]; + var color = colors[random.Next(colors.Length)]; + string animal = animals[random.Next(animals.Length)]; + + // Combine elements with rich text for the color + string colorText = $"{color.Name}"; + string generatedName = $"{adjective}{noun} {title} of the {colorText} {animal}"; + + // Ensure uniqueness by appending a counter + return $"{generatedName}"; + } + } + public static void WriteVectorFloatToBytes(Vector3 values, ref byte[] bytes, ref int offset) + { + EnsureSize(ref bytes, offset + 12); + WriteFloatToBytes(values.x, ref bytes, ref offset);//4 + WriteFloatToBytes(values.y, ref bytes, ref offset);//8 + WriteFloatToBytes(values.z, ref bytes, ref offset);//12 + } + + private unsafe static void WriteFloatToBytes(float value, ref byte[] bytes, ref int offset) + { + // Convert the float to a uint using its bitwise representation + uint intValue = *((uint*)&value); + + // Manually write the bytes + bytes[offset] = (byte)(intValue & 0xFF); + bytes[offset + 1] = (byte)((intValue >> 8) & 0xFF); + bytes[offset + 2] = (byte)((intValue >> 16) & 0xFF); + bytes[offset + 3] = (byte)((intValue >> 24) & 0xFF); + offset += 4; + } + public static int LengthUshortBytes = LocalAvatarSyncMessage.StoredBones * 2; // Initialize LengthBytes first + // Object pool for byte arrays to avoid allocation during runtime + private static readonly ObjectPool byteArrayPool = new ObjectPool(() => new byte[LengthUshortBytes]); + // Ensure the byte array is large enough to hold the data + private static void EnsureSize(ref byte[] bytes, int requiredSize) + { + if (bytes == null || bytes.Length < requiredSize) + { + // Reuse pooled byte arrays + bytes = byteArrayPool.Get(); + Array.Resize(ref bytes, requiredSize); + } + } + + // Ensure the byte array is large enough for reading + private static void EnsureSize(byte[] bytes, int requiredSize) + { + if (bytes.Length < requiredSize) + { + throw new ArgumentException("Byte array is too small for the required size. Current Size is " + bytes.Length + " But Required " + requiredSize); + } + } + // Manual conversion of quaternion to bytes (without BitConverter) + public static void WriteQuaternionToBytes(Quaternion rotation, ref byte[] bytes, ref int offset, BasisRangedUshortFloatData compressor) + { + EnsureSize(ref bytes, offset + 14); + ushort compressedW = compressor.Compress(rotation.value.w); + + // Write the quaternion's components + WriteFloatToBytes(rotation.value.x, ref bytes, ref offset); + WriteFloatToBytes(rotation.value.y, ref bytes, ref offset); + WriteFloatToBytes(rotation.value.z, ref bytes, ref offset); + + // Write the compressed 'w' component + bytes[offset] = (byte)(compressedW & 0xFF); // Low byte + bytes[offset + 1] = (byte)((compressedW >> 8) & 0xFF); // High byte + offset += 2; + } + // Object pool for byte arrays to avoid allocation during runtime + private class ObjectPool + { + private readonly Func createFunc; + private readonly Stack pool; + + public ObjectPool(Func createFunc) + { + this.createFunc = createFunc; + this.pool = new Stack(); + } + + public T Get() + { + return pool.Count > 0 ? pool.Pop() : createFunc(); + } + + public void Return(T item) + { + pool.Push(item); + } + } } } diff --git a/Basis Server/BasisNetworkCore/BasisPlayerArray.cs b/Basis Server/BasisNetworkCore/BasisPlayerArray.cs new file mode 100644 index 000000000..a553529b2 --- /dev/null +++ b/Basis Server/BasisNetworkCore/BasisPlayerArray.cs @@ -0,0 +1,54 @@ +using LiteNetLib; +using System; + +namespace BasisNetworkCore +{ + public class BasisPlayerArray + { + private static readonly object PlayerArrayLock = new object(); + private static NetPeer[] PlayerArray = new NetPeer[4096]; + private static int PlayerCount = 0; + + // Reusable snapshot buffer + private static NetPeer[] SnapshotBuffer = new NetPeer[4096]; + + public static void AddPlayer(NetPeer player) + { + lock (PlayerArrayLock) + { + if (PlayerCount < PlayerArray.Length) + { + PlayerArray[PlayerCount++] = player; + } + } + } + + public static void RemovePlayer(NetPeer player) + { + lock (PlayerArrayLock) + { + for (int i = 0; i < PlayerCount; i++) + { + if (PlayerArray[i] == player) + { + PlayerArray[i] = PlayerArray[--PlayerCount]; // Swap with last element + PlayerArray[PlayerCount] = null; // Clear the last slot + break; + } + } + } + } + + public static ReadOnlySpan GetSnapshot() + { + lock (PlayerArrayLock) + { + // Copy current players into the reusable snapshot buffer + Array.Copy(PlayerArray, 0, SnapshotBuffer, 0, PlayerCount); + + // Return a span of the active players + return new ReadOnlySpan(SnapshotBuffer, 0, PlayerCount); + } + } + } +} diff --git a/Basis Server/BasisNetworkCore/NetDataWriterPool.cs b/Basis Server/BasisNetworkCore/NetDataWriterPool.cs new file mode 100644 index 000000000..3df2eab6d --- /dev/null +++ b/Basis Server/BasisNetworkCore/NetDataWriterPool.cs @@ -0,0 +1,108 @@ +using LiteNetLib.Utils; +using System.Collections.Concurrent; + +namespace BasisNetworkCore +{ + public static class NetDataWriterPool + { + private static readonly ConcurrentBag _pool = new ConcurrentBag(); + private static readonly ConcurrentDictionary> _sizeBuckets = new ConcurrentDictionary>(); + private static readonly int _maxPoolSize = 1000; // Max size of the pool, adjust as needed + private static readonly int _maxBucketSize = 100; // Max size for size-specific buckets, adjust as needed + + /// + /// Retrieves a NetDataWriter from the pool or creates a new one if none are available. + /// Optionally checks for a specific size bucket. + /// + /// The desired size (capacity) of the NetDataWriter buffer. + /// A NetDataWriter instance. + public static NetDataWriter GetWriter(int desiredSize = 0) + { + // Try to find a writer with the desired size + if (desiredSize > 0 && _sizeBuckets.TryGetValue(desiredSize, out var bucket) && bucket.TryTake(out var writer)) + { + writer.Reset(); // Ensure the writer is cleared before reuse. + return writer; + } + + // If no match is found, get a regular writer + if (_pool.TryTake(out var writerFromPool)) + { + writerFromPool.Reset(); // Clear any data from previous use + return writerFromPool; + } + + return new NetDataWriter(true, desiredSize); // Create a new one if the pool is empty + } + + /// + /// Returns a NetDataWriter to the pool for future reuse. + /// + /// The NetDataWriter to return. + public static void ReturnWriter(NetDataWriter writer) + { + writer.Reset(); // Clear data for the next use. + int size = writer.Capacity; // Track the capacity of the writer + + // Only add the writer if the pool hasn't exceeded the maximum size + if (_pool.Count < _maxPoolSize) + { + _pool.Add(writer); + + // Optionally, place in a specific size bucket if it hasn't exceeded the max size + if (size > 0 && _sizeBuckets.TryGetValue(size, out var sizeBucket) && sizeBucket.Count < _maxBucketSize) + { + sizeBucket.Add(writer); + } + else if (size > 0) + { + // Create a new bucket if none exists + var newBucket = new ConcurrentBag(); + newBucket.Add(writer); + _sizeBuckets[size] = newBucket; + } + } + else + { + // If the pool is too large, consider discarding the writer or disposing of it + //dispose baby! writer.Dispose(); // Uncomment if disposable + BNL.LogError("Exceeding Pool Count!"); + } + } + + /// + /// Checks if the pool has any writers with the desired size. + /// + /// The size to check for. + /// True if a writer of the desired size exists in the pool; otherwise, false. + public static bool HasWriterOfSize(int desiredSize) + { + return _sizeBuckets.ContainsKey(desiredSize) && _sizeBuckets[desiredSize].Count > 0; + } + + /// + /// Periodically cleans up the pool to prevent unbounded growth. + /// + public static void CleanUp() + { + // Clean up the pool if it exceeds a maximum size or remove unused writers + if (_pool.Count > _maxPoolSize) + { + // For example, remove items in excess + while (_pool.Count > _maxPoolSize) + { + _pool.TryTake(out _); + } + } + + // Clean up size buckets if they are too large + foreach (var bucket in _sizeBuckets.Values) + { + while (bucket.Count > _maxBucketSize) + { + bucket.TryTake(out _); + } + } + } + } +} diff --git a/Basis Server/BasisNetworkServer/BasisNetworkServer.cs b/Basis Server/BasisNetworkServer/BasisNetworkServer.cs index 66d46a169..0527839a6 100644 --- a/Basis Server/BasisNetworkServer/BasisNetworkServer.cs +++ b/Basis Server/BasisNetworkServer/BasisNetworkServer.cs @@ -3,6 +3,7 @@ using Basis.Network.Server.Auth; using Basis.Network.Server.Generic; using Basis.Network.Server.Ownership; +using BasisNetworkCore; using LiteNetLib; using LiteNetLib.Utils; using System; @@ -11,7 +12,6 @@ using System.Linq; using System.Net; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using static Basis.Network.Core.Serializable.SerializableBasis; using static Basis.Network.Server.Generic.BasisSavedState; @@ -60,6 +60,7 @@ private static void SetupServer(Configuration configuration) DisconnectTimeout = configuration.DisconnectTimeout, PacketPoolSize = 2000, UnsyncedEvents = true, + }; StartListening(configuration); @@ -184,6 +185,7 @@ private static void HandleConnectionRequest(ConnectionRequest request) NetPeer newPeer = request.Accept(); if (Peers.TryAdd((ushort)newPeer.Id, newPeer)) { + BasisPlayerArray.AddPlayer(newPeer); BNL.Log($"Peer connected: {newPeer.Id}"); ReadyMessage readyMessage = new ReadyMessage(); readyMessage.Deserialize(request.Data); @@ -215,6 +217,7 @@ private static void HandlePeerDisconnected(NetPeer peer, DisconnectInfo info) ushort id = (ushort)peer.Id; ClientDisconnect(id, Peers); + BasisPlayerArray.RemovePlayer(peer); if (Peers.TryRemove(id, out _)) { BNL.Log($"Peer removed: {id}"); @@ -278,7 +281,7 @@ private static void HandleNetworkReceiveEvent(NetPeer peer, NetPacketReader read reader.Recycle(); break; case BasisNetworkCommons.SceneChannel: - BasisNetworkingGeneric.HandleScene(reader, deliveryMethod, peer, Peers); + BasisNetworkingGeneric.HandleScene(reader, deliveryMethod, peer); reader.Recycle(); break; case BasisNetworkCommons.AvatarChangeMessage: @@ -286,11 +289,11 @@ private static void HandleNetworkReceiveEvent(NetPeer peer, NetPacketReader read reader.Recycle(); break; case BasisNetworkCommons.OwnershipTransfer: - BasisNetworkOwnership.OwnershipTransfer(reader, peer, Peers); + BasisNetworkOwnership.OwnershipTransfer(reader, peer); reader.Recycle(); break; case BasisNetworkCommons.OwnershipResponse: - BasisNetworkOwnership.OwnershipResponse(reader, peer, Peers); + BasisNetworkOwnership.OwnershipResponse(reader, peer); reader.Recycle(); break; case BasisNetworkCommons.AudioRecipients: @@ -318,22 +321,26 @@ private static void HandleNetworkReceiveEvent(NetPeer peer, NetPacketReader read #region Utility Methods private static void RejectWithReason(ConnectionRequest request, string reason) { - NetDataWriter writer = new NetDataWriter(); + NetDataWriter writer = NetDataWriterPool.GetWriter(); writer.Put(reason); request.Reject(writer); BNL.LogError($"Rejected: {reason}"); + NetDataWriterPool.ReturnWriter(writer); } public static void ClientDisconnect(ushort leaving, ConcurrentDictionary authenticatedClients) { - NetDataWriter writer = new NetDataWriter(); + NetDataWriter writer = NetDataWriterPool.GetWriter(sizeof(ushort)); writer.Put(leaving); foreach (var client in authenticatedClients.Values) { if (client.Id != leaving) + { client.Send(writer, BasisNetworkCommons.Disconnection, DeliveryMethod.ReliableOrdered); + } } + NetDataWriterPool.ReturnWriter(writer); } #endregion private static void SendAvatarMessageToClients(NetPacketReader Reader, NetPeer Peer) @@ -349,9 +356,10 @@ private static void SendAvatarMessageToClients(NetPacketReader Reader, NetPeer P } }; BasisSavedState.AddLastData(Peer, ClientAvatarChangeMessage); - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); serverAvatarChangeMessage.Serialize(Writer); - BroadcastMessageToClients(Writer, BasisNetworkCommons.AvatarChangeMessage, Peer, Peers); + BroadcastMessageToClients(Writer, BasisNetworkCommons.AvatarChangeMessage, Peer,BasisPlayerArray.GetSnapshot()); + NetDataWriterPool.ReturnWriter(Writer); } private static void UpdateVoiceReceivers(NetPacketReader Reader, NetPeer Peer) { @@ -404,38 +412,41 @@ private static void SendVoiceMessageToClients(ServerAudioSegmentMessage audioSeg { playerID = (ushort)sender.Id }; - NetDataWriter NetDataWriter = new NetDataWriter(); + NetDataWriter NetDataWriter = NetDataWriterPool.GetWriter(); audioSegment.Serialize(NetDataWriter); // BNL.Log("Sending Voice Data To Clients"); - BroadcastMessageToClients(NetDataWriter, channel, endPoints, DeliveryMethod.Sequenced); + BroadcastMessageToClients(NetDataWriter, channel,ref endPoints, DeliveryMethod.Sequenced); + NetDataWriterPool.ReturnWriter(NetDataWriter); } else { BNL.Log("Error unable to find " + sender.Id + " in the data store!"); } } - public static void BroadcastMessageToClients(NetDataWriter Reader, byte channel, NetPeer sender, ConcurrentDictionary authenticatedClients, DeliveryMethod deliveryMethod = DeliveryMethod.Sequenced) + public static void BroadcastMessageToClients(NetDataWriter Reader, byte channel, NetPeer sender, ReadOnlySpan authenticatedClients, DeliveryMethod deliveryMethod = DeliveryMethod.Sequenced) { - IEnumerable> clientsExceptSender = authenticatedClients.Where(client => client.Value.Id != sender.Id); - - foreach (KeyValuePair client in clientsExceptSender) + foreach (NetPeer client in authenticatedClients) { - client.Value.Send(Reader, channel, deliveryMethod); + if (client.Id != sender.Id) + { + client.Send(Reader, channel, deliveryMethod); + } } } - public static void BroadcastMessageToClients(NetDataWriter Reader, byte channel, List authenticatedClients, DeliveryMethod deliveryMethod = DeliveryMethod.Sequenced) + public static void BroadcastMessageToClients(NetDataWriter Reader, byte channel, ReadOnlySpan authenticatedClients, DeliveryMethod deliveryMethod = DeliveryMethod.Sequenced) { - int count = authenticatedClients.Count; + int count = authenticatedClients.Length; for (int index = 0; index < count; index++) { authenticatedClients[index].Send(Reader, channel, deliveryMethod); } } - public static void BroadcastMessageToClients(NetDataWriter Reader, byte channel, ConcurrentDictionary authenticatedClients, DeliveryMethod deliveryMethod = DeliveryMethod.Sequenced) + public static void BroadcastMessageToClients(NetDataWriter Reader, byte channel,ref List authenticatedClients, DeliveryMethod deliveryMethod = DeliveryMethod.Sequenced) { - foreach (KeyValuePair client in authenticatedClients) + int count = authenticatedClients.Count; + for (int index = 0; index < count; index++) { - client.Value.Send(Reader, channel, deliveryMethod); + authenticatedClients[index].Send(Reader, channel, deliveryMethod); } } private static void HandleAvatarMovement(NetPacketReader Reader, NetPeer Peer) @@ -443,7 +454,8 @@ private static void HandleAvatarMovement(NetPacketReader Reader, NetPeer Peer) LocalAvatarSyncMessage LocalAvatarSyncMessage = new LocalAvatarSyncMessage(); LocalAvatarSyncMessage.Deserialize(Reader); BasisSavedState.AddLastData(Peer, LocalAvatarSyncMessage); - foreach (NetPeer client in Peers.Values) + ReadOnlySpan Peers = BasisPlayerArray.GetSnapshot(); + foreach (NetPeer client in Peers) { if (client.Id == Peer.Id) { @@ -480,17 +492,20 @@ public static ServerReadyMessage LoadInitialState(NetPeer authClient, ReadyMessa } private static void NotifyExistingClients(ServerReadyMessage serverSideSyncPlayerMessage, NetPeer authClient) { - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); serverSideSyncPlayerMessage.Serialize(Writer); - IEnumerable clientsToNotify = Peers.Values.Where(client => client != authClient); - - string ClientIds = string.Empty; - foreach (NetPeer client in clientsToNotify) + ReadOnlySpan Peers = BasisPlayerArray.GetSnapshot(); + // string ClientIds = string.Empty; + foreach (NetPeer client in Peers) { - ClientIds += $" | {client.Id}"; - client.Send(Writer, BasisNetworkCommons.CreateRemotePlayer, DeliveryMethod.ReliableOrdered); + if (client != authClient) + { + // ClientIds += $" | {client.Id}"; + client.Send(Writer, BasisNetworkCommons.CreateRemotePlayer, DeliveryMethod.ReliableOrdered); + } } - BNL.Log($"Sent Remote Spawn request to {ClientIds}"); + NetDataWriterPool.ReturnWriter(Writer); + // BNL.Log($"Sent Remote Spawn request to {ClientIds}"); } private static void SendClientListToNewClient(NetPeer authClient) { @@ -537,9 +552,10 @@ private static void SendClientListToNewClient(NetPeer authClient) { serverSidePlayer = copied.ToArray(), }; - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); remoteMessages.Serialize(Writer); BNL.Log($"Sending list of clients to {authClient.Id}"); authClient.Send(Writer, BasisNetworkCommons.CreateRemotePlayers, DeliveryMethod.ReliableOrdered); + NetDataWriterPool.ReturnWriter(Writer); } } diff --git a/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkOwnership.cs b/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkOwnership.cs index 927e03daa..9787b43e0 100644 --- a/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkOwnership.cs +++ b/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkOwnership.cs @@ -1,4 +1,5 @@ using Basis.Network.Core; +using BasisNetworkCore; using DarkRift.Basis_Common.Serializable; using LiteNetLib; using LiteNetLib.Utils; @@ -14,7 +15,7 @@ public static class BasisNetworkOwnership public static ConcurrentDictionary ownershipByObjectId = new ConcurrentDictionary(); public static readonly object LockObject = new object(); // For synchronized multi-step operations - public static void OwnershipResponse(NetPacketReader Reader, NetPeer Peer, ConcurrentDictionary allClients) + public static void OwnershipResponse(NetPacketReader Reader, NetPeer Peer) { try { @@ -24,11 +25,12 @@ public static void OwnershipResponse(NetPacketReader Reader, NetPeer Peer, Concu //the goal here is to make it so ownership understanding has to be requested. //once a ownership has been requested there good for life or when a ownership switch happens. NetworkRequestNewOrExisting(ownershipTransferMessage, out ushort currentOwner); - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); ownershipTransferMessage.playerIdMessage.playerID = currentOwner; ownershipTransferMessage.Serialize(Writer); BNL.Log("OwnershipResponse " + currentOwner + " for " + ownershipTransferMessage.playerIdMessage); Peer.Send(Writer, BasisNetworkCommons.OwnershipResponse, DeliveryMethod.ReliableSequenced); + NetDataWriterPool.ReturnWriter(Writer); } catch (Exception ex) { @@ -38,14 +40,14 @@ public static void OwnershipResponse(NetPacketReader Reader, NetPeer Peer, Concu /// /// Handles the ownership transfer for all clients with proper error handling. /// - public static void OwnershipTransfer(NetPacketReader Reader, NetPeer Peer, ConcurrentDictionary allClients) + public static void OwnershipTransfer(NetPacketReader Reader, NetPeer Peer) { try { OwnershipTransferMessage ownershipTransferMessage = new OwnershipTransferMessage(); ownershipTransferMessage.Deserialize(Reader); ushort ClientId = (ushort)Peer.Id; - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); //all clients need to know about a ownership switch if (SwitchOwnership(ownershipTransferMessage.ownershipID, ClientId)) { @@ -53,7 +55,7 @@ public static void OwnershipTransfer(NetPacketReader Reader, NetPeer Peer, Concu ownershipTransferMessage.Serialize(Writer); BNL.Log("OwnershipResponse " + ownershipTransferMessage.ownershipID + " for " + ownershipTransferMessage.playerIdMessage); - BasisNetworkServer.BroadcastMessageToClients(Writer, BasisNetworkCommons.OwnershipTransfer, allClients, DeliveryMethod.ReliableSequenced); + BasisNetworkServer.BroadcastMessageToClients(Writer, BasisNetworkCommons.OwnershipTransfer,BasisPlayerArray.GetSnapshot(), DeliveryMethod.ReliableSequenced); } else { @@ -64,6 +66,7 @@ public static void OwnershipTransfer(NetPacketReader Reader, NetPeer Peer, Concu ownershipTransferMessage.Serialize(Writer); Peer.Send(Writer, BasisNetworkCommons.OwnershipTransfer, DeliveryMethod.ReliableSequenced); } + NetDataWriterPool.ReturnWriter(Writer); } catch (Exception ex) { diff --git a/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkingGeneric.cs b/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkingGeneric.cs index f9e204677..38efe0c55 100644 --- a/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkingGeneric.cs +++ b/Basis Server/BasisNetworkServer/BasisNetworking/BasisNetworkingGeneric.cs @@ -1,14 +1,16 @@ using Basis.Network.Core; +using BasisNetworkCore; using LiteNetLib; using LiteNetLib.Utils; using System.Collections.Concurrent; +using System.Collections.Generic; using static SerializableBasis; namespace Basis.Network.Server.Generic { public static class BasisNetworkingGeneric { - public static void HandleScene(NetPacketReader Reader, DeliveryMethod DeliveryMethod, NetPeer sender, ConcurrentDictionary allClients) + public static void HandleScene(NetPacketReader Reader, DeliveryMethod DeliveryMethod, NetPeer sender) { SceneDataMessage SceneDataMessage = new SceneDataMessage(); SceneDataMessage.Deserialize(Reader); @@ -25,7 +27,7 @@ public static void HandleScene(NetPacketReader Reader, DeliveryMethod DeliveryMe } }; byte Channel = BasisNetworkCommons.SceneChannel; - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); if (DeliveryMethod == DeliveryMethod.Unreliable) { Writer.Put(Channel); @@ -34,16 +36,16 @@ public static void HandleScene(NetPacketReader Reader, DeliveryMethod DeliveryMe serverSceneDataMessage.Serialize(Writer); if (SceneDataMessage.recipientsSize != 0) { - ConcurrentDictionary targetedClients = new ConcurrentDictionary(); + List targetedClients = new List(); int recipientsLength = SceneDataMessage.recipientsSize; - // BNL.Log("Query Recipients " + recipientsLength); + // BNL.Log("Query Recipients " + recipientsLength); for (int index = 0; index < recipientsLength; index++) { if (BasisNetworkServer.Peers.TryGetValue(SceneDataMessage.recipients[index], out NetPeer client)) { BNL.Log("Found Peer! " + SceneDataMessage.recipients[index]); - targetedClients.TryAdd((ushort)client.Id, client); + targetedClients.Add(client); } else { @@ -53,14 +55,15 @@ public static void HandleScene(NetPacketReader Reader, DeliveryMethod DeliveryMe if (targetedClients.Count > 0) { - // BNL.Log("Sending out Target Clients " + targetedClients.Count); - BasisNetworkServer.BroadcastMessageToClients(Writer, Channel, targetedClients, DeliveryMethod); + // BNL.Log("Sending out Target Clients " + targetedClients.Count); + BasisNetworkServer.BroadcastMessageToClients(Writer, Channel,ref targetedClients, DeliveryMethod); } } else { - BasisNetworkServer.BroadcastMessageToClients(Writer, Channel, sender, BasisNetworkServer.Peers, DeliveryMethod); + BasisNetworkServer.BroadcastMessageToClients(Writer, Channel, sender,BasisPlayerArray.GetSnapshot(), DeliveryMethod); } + NetDataWriterPool.ReturnWriter(Writer); } public static void HandleAvatar(NetPacketReader Reader, DeliveryMethod DeliveryMethod, NetPeer sender) { @@ -80,7 +83,7 @@ public static void HandleAvatar(NetPacketReader Reader, DeliveryMethod DeliveryM } }; byte Channel = BasisNetworkCommons.AvatarChannel; - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); if (DeliveryMethod == DeliveryMethod.Unreliable) { Writer.Put(Channel); @@ -89,7 +92,7 @@ public static void HandleAvatar(NetPacketReader Reader, DeliveryMethod DeliveryM serverAvatarDataMessage.Serialize(Writer); if (avatarDataMessage.recipientsSize != 0) { - ConcurrentDictionary targetedClients = new ConcurrentDictionary(); + List targetedClients = new List(); int recipientsLength = avatarDataMessage.recipientsSize; // BNL.Log("Query Recipients " + recipientsLength); @@ -98,7 +101,7 @@ public static void HandleAvatar(NetPacketReader Reader, DeliveryMethod DeliveryM if (BasisNetworkServer.Peers.TryGetValue(avatarDataMessage.recipients[index], out NetPeer client)) { // BNL.Log("Found Peer! " + avatarDataMessage.recipients[index]); - targetedClients.TryAdd((ushort)client.Id, client); + targetedClients.Add(client); } else { @@ -109,13 +112,14 @@ public static void HandleAvatar(NetPacketReader Reader, DeliveryMethod DeliveryM if (targetedClients.Count > 0) { //BNL.Log("Sending out Target Clients " + targetedClients.Count); - BasisNetworkServer.BroadcastMessageToClients(Writer, Channel, targetedClients, DeliveryMethod); + BasisNetworkServer.BroadcastMessageToClients(Writer, Channel,ref targetedClients, DeliveryMethod); } } else { - BasisNetworkServer.BroadcastMessageToClients(Writer, Channel, sender, BasisNetworkServer.Peers, DeliveryMethod); + BasisNetworkServer.BroadcastMessageToClients(Writer, Channel, sender, BasisPlayerArray.GetSnapshot(), DeliveryMethod); } + NetDataWriterPool.ReturnWriter(Writer); } } } diff --git a/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs b/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs index 076f8e06a..d9dde0caa 100644 --- a/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs +++ b/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs @@ -3,6 +3,7 @@ using Basis.Network.Core; using Basis.Network.Core.Compression; using Basis.Scripts.Networking.Compression; +using BasisNetworkCore; using LiteNetLib; using LiteNetLib.Utils; using static SerializableBasis; @@ -172,7 +173,7 @@ private void SendPlayerData(object state) { Console.WriteLine("Unable to find Pulse for LocalClient Wont Do Interval Adjust"); } - NetDataWriter Writer = new NetDataWriter(); + NetDataWriter Writer = NetDataWriterPool.GetWriter(); if (playerData.serverSideSyncPlayerMessage.avatarSerialization.array == null || playerData.serverSideSyncPlayerMessage.avatarSerialization.array.Length == 0) { Console.WriteLine("Unable to send out Avatar Data Was null or Empty!"); @@ -183,14 +184,14 @@ private void SendPlayerData(object state) playerID.localClient.Send(Writer, BasisNetworkCommons.MovementChannel, DeliveryMethod.Sequenced); } playerData.newDataExists = false; + NetDataWriterPool.ReturnWriter(Writer); } } } } public static float Distance(Vector3 pointA, Vector3 pointB) { - Vector3 difference = pointB - pointA; - return difference.SquaredMagnitude(); + return (pointB - pointA).SquaredMagnitude(); // Avoid intermediate objects if possible } /// diff --git a/Basis Server/LiteNetLib/PooledPacket.cs b/Basis Server/LiteNetLib/PooledPacket.cs index 26ef7bd82..1d2daa746 100644 --- a/Basis Server/LiteNetLib/PooledPacket.cs +++ b/Basis Server/LiteNetLib/PooledPacket.cs @@ -1,4 +1,4 @@ -namespace LiteNetLib +namespace LiteNetLib { public readonly ref struct PooledPacket {