using UnityEngine; namespace Crosstales.Common.Util { /// Memory cache stream. public class MemoryCacheStream : System.IO.Stream { #region Variables /// The cache as byte[] private byte[] cache; /// The write position within the stream. private long writePosition; /// The read position within the stream. private long readPosition; /// Stream length. Indicates where the end of the stream is. private long length; /// Cache size in bytes private int size; /// Maximum cache size in bytes private readonly int maxSize; #endregion #region Constructors /// Constructor with a specified cache size. /// Cache size of the stream in bytes. /// Maximum cache size of the stream in bytes. public MemoryCacheStream(int cacheSize = 64 * BaseConstants.FACTOR_KB, int maxCacheSize = 64 * BaseConstants.FACTOR_MB) { length = writePosition = readPosition = 0; size = cacheSize; maxSize = maxCacheSize; createCache(); //Debug.Log("MemoryCacheStream: " + cacheSize + "-" + maxCacheSize); } #endregion #region Stream Overrides [Properties] /// Gets a flag flag that indicates if the stream is readable (always true). public override bool CanRead => true; /// Gets a flag flag that indicates if the stream is seekable (always true). public override bool CanSeek => true; /// Gets a flag flag that indicates if the stream is seekable (always true). public override bool CanWrite => true; /// Gets or sets the current stream position. public override long Position { get => readPosition; set { if (value < 0L) { throw new System.ArgumentOutOfRangeException(nameof(value), "Non-negative number required."); } //writePosition = readPosition = value; //TODO only readPosition? readPosition = value; } } /// Gets the current stream length. public override long Length => length; #endregion #region Stream Overrides [Methods] public override void Flush() { // Memory based stream with nothing to flush; Do nothing. } public override long Seek(long offset, System.IO.SeekOrigin origin) { switch (origin) { case System.IO.SeekOrigin.Begin: { Position = (int)offset; break; } case System.IO.SeekOrigin.Current: { long newPos = unchecked(Position + offset); if (newPos < 0L) throw new System.IO.IOException("An attempt was made to move the position before the beginning of the stream."); Position = newPos; break; } case System.IO.SeekOrigin.End: { long newPos = unchecked(length + offset); if (newPos < 0L) throw new System.IO.IOException("An attempt was made to move the position before the beginning of the stream."); Position = newPos; break; } default: { throw new System.ArgumentException("Invalid seek origin."); } } return Position; } public override void SetLength(long value) { int _size = (int)value; if (size != _size) { size = _size; length = Position = 0; createCache(); } } public override int Read(byte[] buffer, int offset, int count) { if (null == buffer) throw new System.ArgumentNullException(nameof(buffer), "Buffer cannot be null."); if (offset < 0) throw new System.ArgumentOutOfRangeException(nameof(offset), "Non-negative number required."); if (count < 0) throw new System.ArgumentOutOfRangeException(nameof(count), "Non-negative number required."); if (buffer.Length - offset < count) { throw new System.ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); } // Test for end of stream (or beyond end) return readPosition >= length ? 0 : read(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) { if (null == buffer) throw new System.ArgumentNullException(nameof(buffer), "Buffer cannot be null."); if (offset < 0) throw new System.ArgumentOutOfRangeException(nameof(offset), "Non-negative number required."); if (count < 0) throw new System.ArgumentOutOfRangeException(nameof(count), "Non-negative number required."); if (count > size) throw new System.ArgumentOutOfRangeException(nameof(count), "Value is larger than the cache size."); if (buffer.Length - offset < count) { throw new System.ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); } if (0 == count) { // Nothing to do. return; } write(buffer, offset, count); } #endregion #region Private methods /// Read bytes from the memory stream into the provided buffer. private int read(byte[] buff, int offset, int count) { int arrayPosition = (int)(readPosition % size); if (arrayPosition + count > size) { int countEnd = size - arrayPosition; int countStart = count - countEnd; System.Array.Copy(cache, arrayPosition, buff, offset, countEnd); System.Array.Copy(cache, 0, buff, offset + countEnd, countStart); } else { System.Array.Copy(cache, arrayPosition, buff, offset, count); } readPosition += count; return count; } /// Write bytes into the memory stream. private void write(byte[] buff, int offset, int count) { int arrayPosition = (int)(writePosition % size); if (arrayPosition + count > size) { int countEnd = size - arrayPosition; int countStart = count - countEnd; System.Array.Copy(buff, offset, cache, arrayPosition, countEnd); System.Array.Copy(buff, offset + countEnd, cache, 0, countStart); } else { System.Array.Copy(buff, offset, cache, arrayPosition, count); } writePosition += count; length = writePosition; } /// Create the cache private void createCache() { if (size > maxSize) { cache = new byte[maxSize]; Debug.LogWarning("'size' is larger than 'maxSize'! Using 'maxSize' as cache!"); } else { cache = new byte[size]; } } #endregion } } // © 2016-2020 crosstales LLC (https://www.crosstales.com)