using FishNet.Managing; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using GameKit.Dependencies.Utilities; namespace FishNet.Serializing { /// /// Writer which is reused to save on garbage collection and performance. /// public sealed class PooledWriter : Writer { public void Store() => WriterPool.Store(this); public void StoreLength() => WriterPool.StoreLength(this); [Obsolete("Use Clear instead.")] public void ResetState() => base.Clear(); [Obsolete("This does not function.")] public void InitializeState() { } } /// /// Collection of PooledWriter. Stores and gets PooledWriter. /// public static class WriterPool { #region Private. /// /// Pool of writers where length is the minimum and increased at runtime. /// private static readonly Stack _pool = new(); /// /// Pool of writers where length is of minimum key and may be increased at runtime. /// private static readonly Dictionary> _lengthPool = new(); #endregion #region Const. /// /// Length of each bracket when using the length based writer pool. /// internal const int LENGTH_BRACKET = 1000; #endregion /// /// Gets a writer from the pool. /// public static PooledWriter Retrieve(NetworkManager networkManager) { PooledWriter result; if (!_pool.TryPop(out result)) result = new(); result.Clear(networkManager); return result; } /// Gets a writer from the pool. /// public static PooledWriter Retrieve() { return Retrieve(null); } /// /// Gets the next writer in the pool of minimum length. /// /// Minimum length the writer buffer must be. public static PooledWriter Retrieve(int length) { return Retrieve(null, length); } /// /// Gets the next writer in the pool of minimum length. /// /// Minimum length the writer buffer must be. public static PooledWriter Retrieve(NetworkManager networkManager, int length) { /* The index returned will be for writers which have * length as a minimum capacity. * EG: if length is 1200 / 1000 (length_bracket) result * will be index 1. Index 0 will be up to 1000, while * index 1 will be up to 2000. */ int index = GetDictionaryIndex(length); Stack stack; PooledWriter result; //There is already one pooled. if (_lengthPool.TryGetValue(index, out stack) && stack.TryPop(out result)) { result.Clear(networkManager); } //Not pooled yet or failed to pop. else { //Get any ol' writer. result = Retrieve(networkManager); /* Ensure length to fill it's bracket. * Increase index by 1 since 0 index would * just return 0 as the capacity. */ int requiredCapacity = (index + 1) * LENGTH_BRACKET; result.EnsureBufferCapacity(requiredCapacity); } return result; } /// /// Returns a writer to the appropriate length pool. /// Writers must be a minimum of 1000 bytes in length to be sorted by length. /// Writers which do not meet the minimum will be resized to 1000 bytes. /// public static void StoreLength(PooledWriter writer) { int index = GetDictionaryIndex(writer); Stack stack; if (!_lengthPool.TryGetValue(index, out stack)) { stack = new(); _lengthPool[index] = stack; } stack.Push(writer); } /// /// Returns a writer to the pool. /// public static void Store(PooledWriter writer) { _pool.Push(writer); } /// /// Puts writer back into pool if not null, and nullifies source reference. /// public static void StoreAndDefault(ref PooledWriter writer) { if (writer != null) { _pool.Push(writer); writer = null; } } #region Dictionary indexes. /// /// Gets which index to use for length when retrieving a writer. /// private static int GetDictionaryIndex(int length) { /* The index returned will be for writers which have * length as a minimum capacity. * EG: if length is 1200 / 1000 (length_bracket) result * will be index 1. Index 0 will be up to 1000, while * index 1 will be up to 2000. So to accomodate 1200 * length index 1 must be used as 0 has a maximum of 1000. */ /* Examples if length_bracket is 1000, using floor: * 800 / 1000 = 0. * 1200 / 1000 = 1. * 1000 / 1000 = 1. But has 0 remainder so is reduced by 1, resulting in 0. */ int index = UnityEngine.Mathf.FloorToInt(length / LENGTH_BRACKET); if (index > 0 && length % LENGTH_BRACKET == 0) index--; //UnityEngine.Debug.Log($"Returning length {length} from index {index}"); return index; } /// /// Gets which index to use for length when storing a writer. /// private static int GetDictionaryIndex(PooledWriter writer) { int capacity = writer.Capacity; /* If capacity is less than 1000 then the writer * does not meet the minimum length bracket. This should never * be the case unless the user perhaps manually calls this method. */ if (capacity < LENGTH_BRACKET) { capacity = LENGTH_BRACKET; writer.EnsureBufferCapacity(LENGTH_BRACKET); } /* Since capacity is set to minimum of length_bracket * capacity / length_bracket will always be at least 1. * * Here are some result examples using floor: * 1000 / 1000 = 1. * 1200 / 1000 = 1. * 2400 / 1000 = 2. */ int index = UnityEngine.Mathf.FloorToInt(capacity / LENGTH_BRACKET); /* As mentioned the index will always be a minimum of 1. Because of this * we can safely reduce index by 1 and it not be negative. * This reduction also ensures the writer ends up in the proper pool. * Since index 0 ensures minimum of 1000, 1000-1999 would go there. * Just as 2000-2999 would go into 1. */ index--; //UnityEngine.Debug.Log($"Storing capacity {capacity} at index {index}"); return index; } #endregion } }