using FishNet.Managing; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace FishNet.Serializing { /// /// Writer which is reused to save on garbage collection and performance. /// public sealed class PooledWriter : Writer, IDisposable { public void Dispose() => WriterPool.Recycle(this); public void DisposeLength() => WriterPool.RecycleLength(this); } /// /// 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 Stack(); /// /// Pool of writers where length is of minimum key and may be increased at runtime. /// private static readonly Dictionary> _lengthPool = new Dictionary>(); #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 GetWriter(NetworkManager networkManager) { PooledWriter result = (_pool.Count > 0) ? _pool.Pop() : new PooledWriter(); result.Reset(networkManager); return result; } /// /// Gets a writer from the pool. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PooledWriter GetWriter() { return GetWriter(null); } /// /// Gets which index to use for length based pooled readers based on length. /// private static int GetDictionaryIndex(int length) { return (length / LENGTH_BRACKET); } /// /// Gets the next writer in the pool of minimum length. /// /// Minimum length the writer buffer must be. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PooledWriter GetWriter(int length) { return GetWriter(null, length); } /// /// Gets the next writer in the pool of minimum length. /// /// Minimum length the writer buffer must be. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PooledWriter GetWriter(NetworkManager networkManager, int length) { //Ensure length is the minimum. if (length < LENGTH_BRACKET) length = LENGTH_BRACKET; /* 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 dictIndex = GetDictionaryIndex(length); Stack stack; //There is already one pooled. if (_lengthPool.TryGetValue(dictIndex, out stack) && stack.Count > 0) { PooledWriter result = stack.Pop(); result.Reset(networkManager); return result; } //Not pooled yet. else { //Get any ol' writer. PooledWriter writer = GetWriter(networkManager); /* Ensure length to fill it's bracket. * Increase index by 1 since 0 index would * just return 0 as the capacity. */ int requiredCapacity = (dictIndex + 1) * LENGTH_BRACKET; writer.EnsureBufferCapacity(requiredCapacity); return writer; } } /// /// 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 RecycleLength(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); } /* When getting the recycle index subtract one from * the dictionary index. This is because the writer being * recycled must meet the minimum for that index. * EG: if LENGTH_BRACKET is 1000.... * 1200 / 1000 = 1(after flooring). * However, each incremeent in index should have a capacity * of 1000, so index 1 should have a minimum capacity of 2000, * which 1200 does not meet. By subtracting 1 from the index * 1200 will now be placed in index 0 meeting the capacity for that index. */ int dictIndex = GetDictionaryIndex(capacity) - 1; Stack stack; if (!_lengthPool.TryGetValue(dictIndex, out stack)) { stack = new Stack(); _lengthPool[dictIndex] = stack; } stack.Push(writer); } /// /// Returns a writer to the pool. /// public static void Recycle(PooledWriter writer) { _pool.Push(writer); } } }