259 lines
8.5 KiB
C#
259 lines
8.5 KiB
C#
using FishNet.Managing;
|
|
using FishNet.Managing.Logging;
|
|
using FishNet.Managing.Transporting;
|
|
using FishNet.Serializing;
|
|
using FishNet.Utility.Performance;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace FishNet.Connection
|
|
{
|
|
/// <summary>
|
|
/// A byte buffer that automatically resizes.
|
|
/// </summary>
|
|
internal class ByteBuffer
|
|
{
|
|
/// <summary>
|
|
/// How many more bytes may fit into the buffer.
|
|
/// </summary>
|
|
internal int Remaining => (Size - Length);
|
|
/// <summary>
|
|
/// Buffer data.
|
|
/// </summary>
|
|
internal byte[] Data { get; private set; }
|
|
/// <summary>
|
|
/// How many bytes currently into Data. This will include the reserve.
|
|
/// </summary>
|
|
internal int Length { get; private set; }
|
|
/// <summary>
|
|
/// Size of the buffer. Data.Length may exceed this value as it uses a pooled array.
|
|
/// </summary>
|
|
internal int Size { get; private set; }
|
|
/// <summary>
|
|
/// True if data has been written.
|
|
/// </summary>
|
|
internal bool HasData { get; private set; }
|
|
/// <summary>
|
|
/// Bytes to reserve when resetting.
|
|
/// </summary>
|
|
private int _reserve;
|
|
|
|
internal ByteBuffer(int size, int reserve = 0)
|
|
{
|
|
Data = ByteArrayPool.Retrieve(size);
|
|
Size = size;
|
|
_reserve = reserve;
|
|
Reset();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Data != null)
|
|
ByteArrayPool.Store(Data);
|
|
Data = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets instance without clearing Data.
|
|
/// </summary>
|
|
internal void Reset()
|
|
{
|
|
Length = _reserve;
|
|
HasData = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies segments without error checking, including tick for the first time data is added.
|
|
/// </summary>
|
|
/// <param name="segment"></param>
|
|
internal void CopySegment(uint tick, ArraySegment<byte> segment)
|
|
{
|
|
/* If data has not been written to buffer yet
|
|
* then write tick to the start. */
|
|
if (!HasData)
|
|
{
|
|
int pos = 0;
|
|
WriterExtensions.WriteUInt32(Data, tick, ref pos);
|
|
}
|
|
|
|
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
|
|
Length += segment.Count;
|
|
HasData = true;
|
|
}
|
|
/// <summary>
|
|
/// Copies segments without error checking.
|
|
/// </summary>
|
|
/// <param name="segment"></param>
|
|
internal void CopySegment(ArraySegment<byte> segment)
|
|
{
|
|
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
|
|
Length += segment.Count;
|
|
HasData = true;
|
|
}
|
|
|
|
}
|
|
|
|
internal class PacketBundle
|
|
{
|
|
/// <summary>
|
|
/// True if data has been written.
|
|
/// </summary>
|
|
internal bool HasData => _buffers[0].HasData;
|
|
/// <summary>
|
|
/// All buffers written. Collection is not cleared when reset but rather the index in which to write is.
|
|
/// </summary>
|
|
private List<ByteBuffer> _buffers = new List<ByteBuffer>();
|
|
/// <summary>
|
|
/// Buffer which is being written to.
|
|
/// </summary>
|
|
private int _bufferIndex;
|
|
/// <summary>
|
|
/// Maximum size packet the transport can handle.
|
|
/// </summary>
|
|
private int _maximumTransportUnit;
|
|
/// <summary>
|
|
/// Number of buffers written to. Will return 0 if nothing has been written.
|
|
/// </summary>
|
|
public int WrittenBuffers => (!HasData) ? 0 : (_bufferIndex + 1);
|
|
/// <summary>
|
|
/// Number of bytes to reserve at the beginning of each buffer.
|
|
/// </summary>
|
|
private int _reserve;
|
|
/// <summary>
|
|
/// NetworkManager this is for.
|
|
/// </summary>
|
|
private NetworkManager _networkManager;
|
|
|
|
internal PacketBundle(NetworkManager manager, int mtu, int reserve = 0)
|
|
{
|
|
//Allow bytes for the tick.
|
|
reserve += TransportManager.TICK_BYTES;
|
|
|
|
_networkManager = manager;
|
|
_maximumTransportUnit = mtu;
|
|
_reserve = reserve;
|
|
AddBuffer();
|
|
|
|
Reset();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
for (int i = 0; i < _buffers.Count; i++)
|
|
_buffers[i].Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a buffer using current settings.
|
|
/// </summary>
|
|
private ByteBuffer AddBuffer()
|
|
{
|
|
ByteBuffer ba = new ByteBuffer(_maximumTransportUnit, _reserve);
|
|
_buffers.Add(ba);
|
|
return ba;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets using current settings.
|
|
/// </summary>
|
|
internal void Reset()
|
|
{
|
|
_bufferIndex = 0;
|
|
|
|
for (int i = 0; i < _buffers.Count; i++)
|
|
_buffers[i].Reset();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a segment to this packet bundle using the current WriteIndex.
|
|
/// </summary>
|
|
/// <param name="forceNewBuffer">True to force data into a new buffer.</param>
|
|
internal void Write(ArraySegment<byte> segment, bool forceNewBuffer = false)
|
|
{
|
|
//Nothing to be written.
|
|
if (segment.Count == 0)
|
|
return;
|
|
|
|
/* If the segment count is larger than the mtu then
|
|
* something went wrong. Nothing should call this method
|
|
* directly except the TransportManager, which will automatically
|
|
* split packets that exceed MTU into reliable ordered. */
|
|
if (segment.Count > _maximumTransportUnit)
|
|
{
|
|
_networkManager.LogError($"Segment is length of {segment.Count} while MTU is {_maximumTransportUnit}. Packet was not split properly and will not be sent.");
|
|
return;
|
|
}
|
|
|
|
ByteBuffer ba = _buffers[_bufferIndex];
|
|
/* Make a new buffer if...
|
|
* forcing a new buffer and data has already been written to the current.
|
|
* or---
|
|
* segment.Count is more than what is remaining in the buffer. */
|
|
bool useNewBuffer = (forceNewBuffer && ba.Length > _reserve) ||
|
|
(segment.Count > ba.Remaining);
|
|
if (useNewBuffer)
|
|
{
|
|
_bufferIndex++;
|
|
//If need to make a new buffer then do so.
|
|
if (_buffers.Count <= _bufferIndex)
|
|
{
|
|
ba = AddBuffer();
|
|
}
|
|
else
|
|
{
|
|
ba = _buffers[_bufferIndex];
|
|
ba.Reset();
|
|
}
|
|
}
|
|
|
|
uint tick = _networkManager.TimeManager.LocalTick;
|
|
ba.CopySegment(tick, segment);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a buffer for the specified index. Returns true and outputs the buffer if it was successfully found.
|
|
/// </summary>
|
|
/// <param name="index">Index of the buffer to retrieve.</param>
|
|
/// <param name="bb">Buffer retrieved from the list. Null if the specified buffer was not found.</param>
|
|
internal bool GetBuffer(int index, out ByteBuffer bb)
|
|
{
|
|
bb = null;
|
|
|
|
if (index >= _buffers.Count || index < 0)
|
|
{
|
|
_networkManager.LogError($"Index of {index} is out of bounds. There are {_buffers.Count} available.");
|
|
return false;
|
|
}
|
|
if (index > _bufferIndex)
|
|
{
|
|
_networkManager.LogError($"Index of {index} exceeds the number of written buffers. There are {WrittenBuffers} written buffers.");
|
|
return false;
|
|
}
|
|
|
|
bb = _buffers[index];
|
|
return bb.HasData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
|
|
internal static bool GetPacketBundle(int channel, List<PacketBundle> bundles, out PacketBundle mtuBuffer)
|
|
{
|
|
//Out of bounds.
|
|
if (channel >= bundles.Count)
|
|
{
|
|
mtuBuffer = null;
|
|
return false;
|
|
}
|
|
|
|
mtuBuffer = bundles[channel];
|
|
return mtuBuffer.HasData;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
} |