/************************************************************************* * ModernUO * * Copyright 2019-2023 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PacketContainerBuilder.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *************************************************************************/ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using Server.Buffers; namespace Server.Network; public ref struct PacketContainerBuilder { public const int MinPacketLength = 5; private bool _finished; private int _count; private byte[] _arrayToReturnToPool; private Span _bytes; public PacketContainerBuilder(Span initialBuffer) { _arrayToReturnToPool = null; _finished = false; _count = 0; _bytes = initialBuffer; _bytes[0] = 0xF7; // Packet ID Length = MinPacketLength; // Length + Count } public int Length { get; set; } public int Capacity => _bytes.Length; [MethodImpl(MethodImplOptions.NoInlining)] public ReadOnlySpan Finalize() { if (!_finished) { BinaryPrimitives.WriteUInt16BigEndian(_bytes[1..3], (ushort)Length); BinaryPrimitives.WriteUInt16BigEndian(_bytes[3..5], (ushort)_count); } return _bytes[..Length]; } [MethodImpl(MethodImplOptions.NoInlining)] public Span GetSpan(int bytesNeeded) { if (_finished) { throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize"); } if (Length > _bytes.Length - bytesNeeded) { Grow(bytesNeeded); } return _bytes[Length..]; } [MethodImpl(MethodImplOptions.NoInlining)] public void Advance(int bytesWritten) { if (_finished) { throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize"); } _count++; Length += bytesWritten; } [MethodImpl(MethodImplOptions.NoInlining)] private void Grow(int additionalCapacityBeyondPos) { var newLength = Math.Max(Length + additionalCapacityBeyondPos, _bytes.Length * 2); byte[] poolArray = STArrayPool.Shared.Rent(newLength); _bytes[..Length].CopyTo(poolArray); byte[] toReturn = _arrayToReturnToPool; _bytes = _arrayToReturnToPool = poolArray; if (toReturn != null) { STArrayPool.Shared.Return(toReturn); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { byte[] toReturn = _arrayToReturnToPool; this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again if (toReturn != null) { STArrayPool.Shared.Return(toReturn); } } }