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;
}
}