using System; using System.IO; using System.Buffers; namespace Pole.Core.Utils { public class PooledMemoryStream : Stream { /// create writable memory stream with default parameters /// buffer is allocated from ArrayPool.Shared public PooledMemoryStream() : this(ArrayPool.Shared) { } /// create writable memory stream with specified ArrayPool /// buffer is allocated from ArrayPool public PooledMemoryStream(ArrayPool pool) : this(pool, 4096) { } /// create writable memory stream with ensuring buffer length /// buffer is allocated from ArrayPool public PooledMemoryStream(ArrayPool pool, int capacity) { m_Pool = pool; _currentbuffer = m_Pool.Rent(capacity); _Length = 0; _CanWrite = true; _Position = 0; } /// create readonly MemoryStream without buffer copy /// data will be read from 'data' parameter public PooledMemoryStream(byte[] data) { m_Pool = null; _currentbuffer = data; _Length = data.Length; _CanWrite = false; } public override bool CanRead { get { return true; } } public override bool CanSeek => true; public override bool CanWrite => _CanWrite; public override long Length => _Length; public override long Position { get => _Position; set { _Position = value; } } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { int readlen = count > (int)(_Length - _Position) ? (int)(_Length - _Position) : count; if (readlen > 0) { Buffer.BlockCopy(_currentbuffer , (int)_Position , buffer, offset , readlen) ; _Position += readlen; return readlen; } else { return 0; } } public override long Seek(long offset, SeekOrigin origin) { long oldValue = _Position; switch ((int)origin) { case (int)SeekOrigin.Begin: _Position = offset; break; case (int)SeekOrigin.End: _Position = _Length - offset; break; case (int)SeekOrigin.Current: _Position += offset; break; default: throw new InvalidOperationException("unknown SeekOrigin"); } if (_Position < 0 || _Position > _Length) { _Position = oldValue; throw new IndexOutOfRangeException(); } return _Position; } void ReallocateBuffer(int minimumRequired) { var tmp = m_Pool.Rent(minimumRequired); Buffer.BlockCopy(_currentbuffer, 0, tmp, 0, _currentbuffer.Length); m_Pool.Return(_currentbuffer); _currentbuffer = tmp; } public override void SetLength(long value) { if (!_CanWrite) { throw new NotSupportedException("stream is readonly"); } if (value > int.MaxValue) { throw new IndexOutOfRangeException("overflow"); } if (value < 0) { throw new IndexOutOfRangeException("underflow"); } _Length = value; if (_currentbuffer.Length < _Length) { ReallocateBuffer((int)_Length); } } /// write data to stream /// if stream data length is over int.MaxValue, this method throws IndexOutOfRangeException public override void Write(byte[] buffer, int offset, int count) { if (!_CanWrite) { throw new InvalidOperationException("stream is readonly"); } long endOffset = _Position + count; if (endOffset > _currentbuffer.Length) { ReallocateBuffer((int)(endOffset) * 2); } Buffer.BlockCopy(buffer, offset, _currentbuffer, (int)_Position, count); if (endOffset > _Length) { _Length = endOffset; } _Position = endOffset; } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (m_Pool != null && _currentbuffer != null) { m_Pool.Return(_currentbuffer); _currentbuffer = null; } } /// ensure the buffer size /// capacity != stream buffer length public void Reserve(int capacity) { if (capacity > _currentbuffer.Length) { ReallocateBuffer(capacity); } } /// Create newly allocated buffer and copy the stream data public byte[] ToArray() { var ret = new byte[_Length]; Buffer.BlockCopy(_currentbuffer, 0, ret, 0, (int)_Length); return ret; } /// Create ArraySegment for current stream data without allocation buffer /// After disposing stream, manupilating returned value(read or write) may cause undefined behavior public ArraySegment ToUnsafeArraySegment() { return new ArraySegment(_currentbuffer, 0, (int)_Length); } public ReadOnlyMemory ToReadOnlyMemory() { return new ReadOnlyMemory(_currentbuffer, 0, (int)_Length); } ArrayPool m_Pool; byte[] _currentbuffer; readonly bool _CanWrite; long _Length; long _Position; } }