using FishNet.Managing; using FishNet.Managing.Logging; using FishNet.Managing.Transporting; using FishNet.Object; using FishNet.Serializing; using FishNet.Utility.Performance; using System; using System.Collections.Generic; using UnityEngine; namespace FishNet.Connection { /// /// A byte buffer that automatically resizes. /// internal class ByteBuffer { /// /// How many more bytes may fit into the buffer. /// internal int Remaining => (Size - Length); /// /// Buffer data. /// internal byte[] Data { get; private set; } /// /// How many bytes currently into Data. This will include the reserve. /// internal int Length { get; private set; } /// /// Size of the buffer. Data.Length may exceed this value as it uses a pooled array. /// internal int Size { get; private set; } /// /// True if data has been written. /// internal bool HasData { get; private set; } /// /// Bytes to reserve when resetting. /// 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; } /// /// Resets instance without clearing Data. /// internal void Reset() { Length = _reserve; HasData = false; } /// /// Copies segments without error checking, including tick for the first time data is added. /// /// internal void CopySegment(uint tick, ArraySegment segment) { /* If data has not been written to buffer yet * then write tick to the start. */ if (!HasData) { int pos = 0; Writer.WriteUInt32Unpacked(Data, tick, ref pos); } Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count); Length += segment.Count; HasData = true; } /// /// Copies segments without error checking. /// /// internal void CopySegment(ArraySegment segment) { Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count); Length += segment.Count; HasData = true; } } internal class PacketBundle { /// /// True if data has been written. /// internal bool HasData => (_buffers[0].HasData || (!_isSendLastBundle && _sendLastBundle.HasData)); /// /// All buffers written. Collection is not cleared when reset but rather the index in which to write is. /// private List _buffers = new(); /// /// Buffer which is being written to. /// private int _bufferIndex; /// /// Maximum size packet the transport can handle. /// private int _maximumTransportUnit; /// /// Number of buffers written to. Will return 0 if nothing has been written. /// public int WrittenBuffers => (!HasData) ? 0 : (_bufferIndex + 1); /// /// Number of bytes to reserve at the beginning of each buffer. /// private int _reserve; /// /// NetworkManager this is for. /// private NetworkManager _networkManager; /// /// Packet bundle to use for last enqueued data. /// private PacketBundle _sendLastBundle; /// /// True if being used as an sendLast bundle. /// private bool _isSendLastBundle; internal PacketBundle(NetworkManager manager, int mtu, int reserve = 0, DataOrderType orderType = DataOrderType.Default) { _isSendLastBundle = (orderType == DataOrderType.Last); //If this is not the send last packetbundle then make a new one. if (!_isSendLastBundle) _sendLastBundle = new(manager, mtu, reserve, DataOrderType.Last); _networkManager = manager; _maximumTransportUnit = mtu; /* Allow bytes for the tick. * Modify reserve after making sendLast bundle * so that the wrong reserve is not passed into * the sendLast bundle. */ reserve += TransportManager.UNPACKED_TICK_LENGTH; _reserve = reserve; //Add buffer requires the right reserve so call after setting. AddBuffer(); Reset(false); } public void Dispose() { for (int i = 0; i < _buffers.Count; i++) _buffers[i].Dispose(); _sendLastBundle?.Dispose(); } /// /// Adds a buffer using current settings. /// private ByteBuffer AddBuffer() { ByteBuffer ba = new(_maximumTransportUnit, _reserve); _buffers.Add(ba); return ba; } /// /// Resets using current settings. /// internal void Reset(bool resetSendLast) { _bufferIndex = 0; for (int i = 0; i < _buffers.Count; i++) _buffers[i].Reset(); if (resetSendLast) _sendLastBundle.Reset(false); } /// /// Writes a segment to this packet bundle using the current WriteIndex. /// /// True to force data into a new buffer. internal void Write(ArraySegment segment, bool forceNewBuffer = false, DataOrderType orderType = DataOrderType.Default) { /* If not the send last bundle and to send data last * then send using the send last bundle. */ if (!_isSendLastBundle && orderType == DataOrderType.Last) { _sendLastBundle.Write(segment, forceNewBuffer, orderType); return; } //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); } /// /// Returns the packetBundle for send last. /// /// internal PacketBundle GetSendLastBundle() => _sendLastBundle; /// /// Gets a buffer for the specified index. Returns true and outputs the buffer if it was successfully found. /// /// Index of the buffer to retrieve. /// Buffer retrieved from the list. Null if the specified buffer was not found. 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; } /// /// Returns a PacketBundle for a channel. ResetPackets must be called afterwards. /// /// /// True if PacketBundle is valid on the index and contains data. internal static bool GetPacketBundle(int channel, List bundles, out PacketBundle mtuBuffer) { //Out of bounds. if (channel >= bundles.Count) { mtuBuffer = null; return false; } mtuBuffer = bundles[channel]; return mtuBuffer.HasData; } } }