From 73aadd2c37c09f582974f18e25acd1f1d761227e Mon Sep 17 00:00:00 2001 From: dooly Date: Fri, 10 Jan 2025 04:35:58 +1000 Subject: [PATCH] added chunked ChunkedServerSideReducablePlayerArray --- .../BasisServerReductionSystem.cs | 152 +++++++++++------- .../BasisNetworking/ChunkedBoolArray.cs | 61 +++++++ 2 files changed, 153 insertions(+), 60 deletions(-) diff --git a/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs b/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs index 9b9dc47b1..aee8473a3 100644 --- a/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs +++ b/Basis Server/BasisNetworkServer/BasisNetworking/BasisServerReductionSystem.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Concurrent; +using System.Linq; +using System.Threading; using Basis.Network.Core; using Basis.Network.Core.Compression; using Basis.Scripts.Networking.Compression; -using BasisNetworkCore; using LiteNetLib; using LiteNetLib.Utils; using static SerializableBasis; @@ -11,7 +12,7 @@ public partial class BasisServerReductionSystem { // Default interval in milliseconds for the timer public static Configuration Configuration; - public static ConcurrentDictionary PlayerSync = new ConcurrentDictionary(); + public static ConcurrentDictionary PlayerSync = new ConcurrentDictionary(); /// /// add the new client /// then update all existing clients arrays @@ -22,12 +23,13 @@ public partial class BasisServerReductionSystem public static void AddOrUpdatePlayer(NetPeer playerID, ServerSideSyncPlayerMessage playerToUpdate, NetPeer serverSideSyncPlayer) { //stage 1 lets update whoever send us this datas last player information - if (PlayerSync.TryGetValue(serverSideSyncPlayer, out SyncedToPlayerPulse playerData)) + if (PlayerSync.TryGetValue(serverSideSyncPlayer.Id, out SyncedToPlayerPulse playerData)) { playerData.lastPlayerInformation = playerToUpdate; + playerData.Position = BasisNetworkCompressionExtensions.DecompressAndProcessAvatar(playerToUpdate); } //ok now we can try to schedule sending out this data! - if (PlayerSync.TryGetValue(playerID, out playerData)) + if (PlayerSync.TryGetValue(playerID.Id, out playerData)) { // Update the player's message playerData.SupplyNewData(playerID, playerToUpdate, serverSideSyncPlayer); @@ -37,12 +39,12 @@ public static void AddOrUpdatePlayer(NetPeer playerID, ServerSideSyncPlayerMessa //first time request create said data! playerData = new SyncedToPlayerPulse { - // playerID = playerID, - queuedPlayerMessages = new ConcurrentDictionary(), + // playerID = playerID, lastPlayerInformation = playerToUpdate, + Position = BasisNetworkCompressionExtensions.DecompressAndProcessAvatar(playerToUpdate), }; //ok now we can try to schedule sending out this data! - if (PlayerSync.TryAdd(playerID, playerData)) + if (PlayerSync.TryAdd(playerID.Id, playerData)) { // Update the player's message playerData.SupplyNewData(playerID, playerToUpdate, serverSideSyncPlayer); } @@ -50,18 +52,27 @@ public static void AddOrUpdatePlayer(NetPeer playerID, ServerSideSyncPlayerMessa } public static void RemovePlayer(NetPeer playerID) { - if (PlayerSync.TryRemove(playerID, out SyncedToPlayerPulse pulse)) + if (PlayerSync.TryRemove(playerID.Id, out SyncedToPlayerPulse pulse)) { - foreach (ServerSideReducablePlayer player in pulse.queuedPlayerMessages.Values) + for (int i = 0; i < 1024; i++) { - player.timer.Dispose(); + ServerSideReducablePlayer player = pulse.ChunkedServerSideReducablePlayerArray.GetPlayer(i); + if (player != null) + { + player.timer.Dispose(); + pulse.ChunkedServerSideReducablePlayerArray.SetPlayer(i, null); + } } } - foreach (SyncedToPlayerPulse player in PlayerSync.Values) + SyncedToPlayerPulse[] Index = PlayerSync.Values.ToArray(); + for (int i = 0; i < Index.Length; i++) { - if (player.queuedPlayerMessages.TryRemove(playerID, out ServerSideReducablePlayer reduceablePlayer)) + SyncedToPlayerPulse player = Index[i]; + ServerSideReducablePlayer SSRP = player.ChunkedServerSideReducablePlayerArray.GetPlayer(i); + if (SSRP != null) { - reduceablePlayer.timer.Dispose(); + SSRP.timer.Dispose(); + pulse.ChunkedServerSideReducablePlayerArray.SetPlayer(i, null); } } } @@ -73,12 +84,13 @@ public class SyncedToPlayerPulse // The player ID to which the data is being sent // public NetPeer playerID; public ServerSideSyncPlayerMessage lastPlayerInformation; + public Vector3 Position; /// /// Dictionary to hold queued messages for each player. /// Key: Player ID, Value: Server-side player data /// - public ConcurrentDictionary queuedPlayerMessages = new ConcurrentDictionary(); public ChunkedBoolArray SyncBoolArray = new ChunkedBoolArray(64); + public ChunkedServerSideReducablePlayerArray ChunkedServerSideReducablePlayerArray = new ChunkedServerSideReducablePlayerArray(64); /// /// Supply new data to a specific player. /// @@ -87,12 +99,14 @@ public class SyncedToPlayerPulse /// public void SupplyNewData(NetPeer playerID, ServerSideSyncPlayerMessage serverSideSyncPlayerMessage, NetPeer serverSidePlayer) { - if (queuedPlayerMessages.TryGetValue(serverSidePlayer, out ServerSideReducablePlayer playerData)) + ServerSideReducablePlayer playerData = ChunkedServerSideReducablePlayerArray.GetPlayer(serverSidePlayer.Id); + if (playerData != null) { // Update the player's message playerData.serverSideSyncPlayerMessage = serverSideSyncPlayerMessage; + playerData.Position = BasisNetworkCompressionExtensions.DecompressAndProcessAvatar(serverSideSyncPlayerMessage); SyncBoolArray.SetBool(serverSidePlayer.Id, true); - queuedPlayerMessages[serverSidePlayer] = playerData; + ChunkedServerSideReducablePlayerArray.SetPlayer(serverSidePlayer.Id, playerData); } else { @@ -112,34 +126,24 @@ public void AddPlayer(NetPeer playerID, ServerSideSyncPlayerMessage serverSideSy ClientPayload clientPayload = new ClientPayload { localClient = playerID, - dataCameFromThisUser = serverSidePlayer + dataCameFromThisUser = serverSidePlayer.Id }; + serverSideSyncPlayerMessage.interval = (byte)Configuration.BSRSMillisecondDefaultInterval; ServerSideReducablePlayer newPlayer = new ServerSideReducablePlayer { serverSideSyncPlayerMessage = serverSideSyncPlayerMessage, - timer = new System.Threading.Timer(SendPlayerData, clientPayload, Configuration.BSRSMillisecondDefaultInterval, Configuration.BSRSMillisecondDefaultInterval), + timer = new ManagedTimer(SendPlayerData, clientPayload, Configuration.BSRSMillisecondDefaultInterval, Configuration.BSRSMillisecondDefaultInterval), Writer = new NetDataWriter(true, 204), + Position = BasisNetworkCompressionExtensions.DecompressAndProcessAvatar(serverSideSyncPlayerMessage), }; + SendPlayerData(clientPayload); SyncBoolArray.SetBool(serverSidePlayer.Id, true); - queuedPlayerMessages[serverSidePlayer] = newPlayer; - } - - /// - /// Removes a player from the queue and disposes of their timer. - /// - /// The ID of the player to remove - public void RemovePlayer(NetPeer playerID) - { - if (queuedPlayerMessages.TryRemove(playerID, out ServerSideReducablePlayer playerData)) - { - // Dispose of the timer to free resources - playerData.timer.Dispose(); - } + ChunkedServerSideReducablePlayerArray.SetPlayer(serverSidePlayer.Id, newPlayer); } public struct ClientPayload { public NetPeer localClient; - public NetPeer dataCameFromThisUser; + public int dataCameFromThisUser; } /// /// Callback function to send player data at regular intervals. @@ -147,42 +151,42 @@ public struct ClientPayload /// The player ID (passed from the timer) private void SendPlayerData(object state) { - if (state is ClientPayload playerID) + var playerID = (ClientPayload)state; + if (SyncBoolArray.GetBool(playerID.dataCameFromThisUser)) { - if (SyncBoolArray.GetBool(playerID.dataCameFromThisUser.Id)) + ServerSideReducablePlayer playerData = ChunkedServerSideReducablePlayerArray.GetPlayer(playerID.dataCameFromThisUser); + if (playerData != null) { - if (queuedPlayerMessages.TryGetValue(playerID.dataCameFromThisUser, out ServerSideReducablePlayer playerData)) + if (PlayerSync.TryGetValue(playerID.localClient.Id, out SyncedToPlayerPulse pulse)) { - if (PlayerSync.TryGetValue(playerID.localClient, out SyncedToPlayerPulse pulse)) + try { - try + // Calculate the distance between the two points + float activeDistance = Distance(pulse.Position, playerData.Position); + // Adjust the timer interval based on the new syncRateMultiplier + int adjustedInterval = (int)(Configuration.BSRSMillisecondDefaultInterval * (Configuration.BSRBaseMultiplier + (activeDistance * Configuration.BSRSIncreaseRate))); + if (adjustedInterval > byte.MaxValue) { - Vector3 from = BasisNetworkCompressionExtensions.DecompressAndProcessAvatar(pulse.lastPlayerInformation); - Vector3 to = BasisNetworkCompressionExtensions.DecompressAndProcessAvatar(playerData.serverSideSyncPlayerMessage); - // Calculate the distance between the two points - float activeDistance = Distance(from, to); - // Adjust the timer interval based on the new syncRateMultiplier - int adjustedInterval = (int)(Configuration.BSRSMillisecondDefaultInterval * (Configuration.BSRBaseMultiplier + (activeDistance * Configuration.BSRSIncreaseRate))); - if (adjustedInterval > byte.MaxValue) - { - adjustedInterval = byte.MaxValue; - } - // Console.WriteLine("Adjusted Interval is" + adjustedInterval); - playerData.timer.Change(adjustedInterval, adjustedInterval); - //how long does this data need to last for - playerData.serverSideSyncPlayerMessage.interval = (byte)adjustedInterval; - playerData.serverSideSyncPlayerMessage.Serialize(playerData.Writer); - playerID.localClient.Send(playerData.Writer, BasisNetworkCommons.MovementChannel, DeliveryMethod.Sequenced); - playerData.Writer.Reset(); + adjustedInterval = byte.MaxValue; } - catch (Exception e) + if (playerData.serverSideSyncPlayerMessage.interval != adjustedInterval) { - BNL.LogError("Server Reduction System Encounter Isssue " + e.Message + " " + e.StackTrace); + // Console.WriteLine("Adjusted Interval is" + adjustedInterval); + playerData.timer.Change(adjustedInterval, adjustedInterval); } + //how long does this data need to last for + playerData.serverSideSyncPlayerMessage.interval = (byte)adjustedInterval; + playerData.serverSideSyncPlayerMessage.Serialize(playerData.Writer); + playerID.localClient.Send(playerData.Writer, BasisNetworkCommons.MovementChannel, DeliveryMethod.Sequenced); + playerData.Writer.Reset(); + } + catch (Exception e) + { + BNL.LogError("Server Reduction System Encounter Isssue " + e.Message + " " + e.StackTrace); } - SyncBoolArray.SetBool(playerID.dataCameFromThisUser.Id, false); } } + SyncBoolArray.SetBool(playerID.dataCameFromThisUser, false); } } } @@ -194,10 +198,38 @@ public static float Distance(Vector3 pointA, Vector3 pointB) /// /// Structure representing a player's server-side data that can be reduced. /// - public struct ServerSideReducablePlayer + public class ServerSideReducablePlayer { - public System.Threading.Timer timer;//create a new timer + public ManagedTimer timer;//create a new timer public ServerSideSyncPlayerMessage serverSideSyncPlayerMessage; public NetDataWriter Writer; + public Vector3 Position; + } + public class ManagedTimer : IDisposable + { + private Timer _timer; + public bool IsDisposed = false; + + public ManagedTimer(TimerCallback callback, object state, int dueTime, int period) + { + _timer = new System.Threading.Timer(callback, state, dueTime, period); + } + public void Dispose() + { + if (!IsDisposed) + { + _timer.Dispose(); + IsDisposed = true; + } + } + + public bool Change(int dueTime, int period) + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(ManagedTimer)); + } + return _timer.Change(dueTime, period); + } } } diff --git a/Basis Server/BasisNetworkServer/BasisNetworking/ChunkedBoolArray.cs b/Basis Server/BasisNetworkServer/BasisNetworking/ChunkedBoolArray.cs index e30aa4630..ea67990e3 100644 --- a/Basis Server/BasisNetworkServer/BasisNetworking/ChunkedBoolArray.cs +++ b/Basis Server/BasisNetworkServer/BasisNetworking/ChunkedBoolArray.cs @@ -1,4 +1,5 @@ using System; +using static BasisServerReductionSystem; public class ChunkedBoolArray { @@ -60,3 +61,63 @@ public bool GetBool(int index) } } } +public class ChunkedServerSideReducablePlayerArray +{ + private readonly object[] _chunkLocks; // Locks for each chunk + private readonly ServerSideReducablePlayer[][] _chunks; // Array divided into chunks + private readonly int _chunkSize; // Number of elements in each chunk + private readonly int _numChunks; // Number of chunks + public const int TotalSize = 1024; // Total size of the array + + // Precomputed array size + private readonly int _totalSize; + + public ChunkedServerSideReducablePlayerArray(int chunkSize = 256) + { + if (TotalSize <= 0) + throw new ArgumentOutOfRangeException(nameof(TotalSize), "Total size must be greater than zero."); + if (chunkSize <= 0) + throw new ArgumentOutOfRangeException(nameof(chunkSize), "Chunk size must be greater than zero."); + + _chunkSize = chunkSize; + _numChunks = (int)Math.Ceiling((double)TotalSize / chunkSize); + _totalSize = _chunkSize * _numChunks; + + _chunks = new ServerSideReducablePlayer[_numChunks][]; + _chunkLocks = new object[_numChunks]; + + for (int i = 0; i < _numChunks; i++) + { + _chunks[i] = new ServerSideReducablePlayer[chunkSize]; + _chunkLocks[i] = new object(); + } + } + + public void SetPlayer(int index, ServerSideReducablePlayer player) + { + if (index < 0 || index >= _totalSize) + throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); + + int chunkIndex = index / _chunkSize; + int localIndex = index % _chunkSize; + + lock (_chunkLocks[chunkIndex]) + { + _chunks[chunkIndex][localIndex] = player; + } + } + + public ServerSideReducablePlayer GetPlayer(int index) + { + if (index < 0 || index >= _totalSize) + throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); + + int chunkIndex = index / _chunkSize; + int localIndex = index % _chunkSize; + + lock (_chunkLocks[chunkIndex]) + { + return _chunks[chunkIndex][localIndex]; + } + } +}