160 lines
6.0 KiB
C#
160 lines
6.0 KiB
C#
|
using FishNet.Managing;
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
|
||
|
namespace FishNet.Serializing
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Writer which is reused to save on garbage collection and performance.
|
||
|
/// </summary>
|
||
|
public sealed class PooledWriter : Writer, IDisposable
|
||
|
{
|
||
|
public void Dispose() => WriterPool.Recycle(this);
|
||
|
public void DisposeLength() => WriterPool.RecycleLength(this);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Collection of PooledWriter. Stores and gets PooledWriter.
|
||
|
/// </summary>
|
||
|
public static class WriterPool
|
||
|
{
|
||
|
#region Private.
|
||
|
/// <summary>
|
||
|
/// Pool of writers where length is the minimum and increased at runtime.
|
||
|
/// </summary>
|
||
|
private static readonly Stack<PooledWriter> _pool = new Stack<PooledWriter>();
|
||
|
/// <summary>
|
||
|
/// Pool of writers where length is of minimum key and may be increased at runtime.
|
||
|
/// </summary>
|
||
|
private static readonly Dictionary<int, Stack<PooledWriter>> _lengthPool = new Dictionary<int, Stack<PooledWriter>>();
|
||
|
#endregion
|
||
|
|
||
|
#region Const.
|
||
|
/// <summary>
|
||
|
/// Length of each bracket when using the length based writer pool.
|
||
|
/// </summary>
|
||
|
internal const int LENGTH_BRACKET = 1000;
|
||
|
#endregion
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets a writer from the pool.
|
||
|
/// </summary>
|
||
|
public static PooledWriter GetWriter(NetworkManager networkManager)
|
||
|
{
|
||
|
PooledWriter result = (_pool.Count > 0) ? _pool.Pop() : new PooledWriter();
|
||
|
result.Reset(networkManager);
|
||
|
return result;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Gets a writer from the pool.
|
||
|
/// </summary>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public static PooledWriter GetWriter()
|
||
|
{
|
||
|
return GetWriter(null);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets which index to use for length based pooled readers based on length.
|
||
|
/// </summary>
|
||
|
private static int GetDictionaryIndex(int length)
|
||
|
{
|
||
|
return (length / LENGTH_BRACKET);
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Gets the next writer in the pool of minimum length.
|
||
|
/// </summary>
|
||
|
/// <param name="length">Minimum length the writer buffer must be.</param>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public static PooledWriter GetWriter(int length)
|
||
|
{
|
||
|
return GetWriter(null, length);
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Gets the next writer in the pool of minimum length.
|
||
|
/// </summary>
|
||
|
/// <param name="length">Minimum length the writer buffer must be.</param>
|
||
|
[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<PooledWriter> 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
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<PooledWriter> stack;
|
||
|
if (!_lengthPool.TryGetValue(dictIndex, out stack))
|
||
|
{
|
||
|
stack = new Stack<PooledWriter>();
|
||
|
_lengthPool[dictIndex] = stack;
|
||
|
}
|
||
|
|
||
|
stack.Push(writer);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a writer to the pool.
|
||
|
/// </summary>
|
||
|
public static void Recycle(PooledWriter writer)
|
||
|
{
|
||
|
_pool.Push(writer);
|
||
|
}
|
||
|
}
|
||
|
}
|