コード例 #1
0
ファイル: CachedStorage.cs プロジェクト: garoxas/LibHac
        protected override Result DoWrite(long offset, ReadOnlySpan <byte> source)
        {
            long remaining = source.Length;
            long inOffset  = offset;
            int  outOffset = 0;

            if (!IsRangeValid(offset, source.Length, Length))
            {
                return(ResultFs.OutOfRange.Log());
            }

            lock (Blocks)
            {
                while (remaining > 0)
                {
                    long       blockIndex = inOffset / BlockSize;
                    int        blockPos   = (int)(inOffset % BlockSize);
                    CacheBlock block      = GetBlock(blockIndex);

                    int bytesToWrite = (int)Math.Min(remaining, BlockSize - blockPos);

                    source.Slice(outOffset, bytesToWrite).CopyTo(block.Buffer.AsSpan(blockPos));

                    block.Dirty = true;

                    outOffset += bytesToWrite;
                    inOffset  += bytesToWrite;
                    remaining -= bytesToWrite;
                }
            }

            return(Result.Success);
        }
コード例 #2
0
        private void Elevate(CacheBlock block)
        {
            CacheBlock target = block;

            // can we do better? faster find the position to insert
            while (target.Next != null && target.Next.Frequency <= block.Frequency)
            {
                target = target.Next;
            }
            if (target == block)
            {
                return;
            }

            if (block == least)
            {
                least      = block.Next;
                least.Prev = null;
            }
            else
            {
                block.Prev.Next = block.Next;
                block.Next.Prev = block.Prev;
            }

            // insert block after target
            if (target.Next != null)
            {
                target.Next.Prev = block;
            }
            block.Next  = target.Next;
            block.Prev  = target;
            target.Next = block;
        }
コード例 #3
0
ファイル: CachedStorage.cs プロジェクト: garoxas/LibHac
        protected override Result DoRead(long offset, Span <byte> destination)
        {
            long remaining = destination.Length;
            long inOffset  = offset;
            int  outOffset = 0;

            if (!IsRangeValid(offset, destination.Length, Length))
            {
                return(ResultFs.OutOfRange.Log());
            }

            lock (Blocks)
            {
                while (remaining > 0)
                {
                    long       blockIndex = inOffset / BlockSize;
                    int        blockPos   = (int)(inOffset % BlockSize);
                    CacheBlock block      = GetBlock(blockIndex);

                    int bytesToRead = (int)Math.Min(remaining, BlockSize - blockPos);

                    block.Buffer.AsSpan(blockPos, bytesToRead).CopyTo(destination.Slice(outOffset));

                    outOffset += bytesToRead;
                    inOffset  += bytesToRead;
                    remaining -= bytesToRead;
                }
            }

            return(Result.Success);
        }
コード例 #4
0
ファイル: CachedStorage.cs プロジェクト: tckm1977/LibHac
        private CacheBlock GetBlock(long blockIndex)
        {
            if (BlockDict.TryGetValue(blockIndex, out LinkedListNode <CacheBlock> node))
            {
                if (Blocks.First != node)
                {
                    Blocks.Remove(node);
                    Blocks.AddFirst(node);
                }

                return(node.Value);
            }

            node = Blocks.Last;
            FlushBlock(node.Value);

            CacheBlock block = node.Value;

            Blocks.RemoveLast();
            BlockDict.Remove(block.Index);

            FlushBlock(block);
            ReadBlock(block, blockIndex);

            Blocks.AddFirst(node);
            BlockDict.Add(blockIndex, node);

            return(block);
        }
コード例 #5
0
ファイル: CachedStorage.cs プロジェクト: tckm1977/LibHac
        protected override void WriteImpl(ReadOnlySpan <byte> source, long offset)
        {
            long remaining = source.Length;
            long inOffset  = offset;
            int  outOffset = 0;

            lock (Blocks)
            {
                while (remaining > 0)
                {
                    long       blockIndex = inOffset / BlockSize;
                    int        blockPos   = (int)(inOffset % BlockSize);
                    CacheBlock block      = GetBlock(blockIndex);

                    int bytesToWrite = (int)Math.Min(remaining, BlockSize - blockPos);

                    source.Slice(outOffset, bytesToWrite).CopyTo(block.Buffer.AsSpan(blockPos));

                    block.Dirty = true;

                    outOffset += bytesToWrite;
                    inOffset  += bytesToWrite;
                    remaining -= bytesToWrite;
                }
            }
        }
コード例 #6
0
        /// <summary>
        /// 将一个资源注册到缓冲区中
        /// </summary>
        /// <param name="resourceId">资源id</param>
        /// <param name="allocPtr">资源的字节数组</param>
        /// <param name="cType">资源缓存类型</param>
        /// <param name="priority">初始引用计数</param>
        public static void Register(string resourceId, byte[] allocPtr, ResourceCacheType cType, long priority = 1)
        {
            switch (cType)
            {
            case ResourceCacheType.Eden:
                var edenIdx = ResourceCachePool.EdenQueue.FindIndex(x => x.CacheId == resourceId);
                if (edenIdx == -1)
                {
                    var cb = new CacheBlock(resourceId, allocPtr, priority);
                    cb.Referred();
                    ResourceCachePool.EdenQueue.Push(cb);
                }
                else
                {
                    var cBlock = ResourceCachePool.EdenQueue.ElementAt(edenIdx);
                    cBlock.Referred();
                    cBlock.AllocReference = allocPtr;
                    ResourceCachePool.EdenQueue.Exchange(edenIdx, ResourceCachePool.EdenQueue.Count() - 1);
                    ResourceCachePool.EdenQueue.Adjust();
                    ResourceCachePool.EdenQueue.ForEachFrom(GlobalConfigContext.EdenResourceCacheSize, t => t.Abandon());
                }
                break;

            case ResourceCacheType.Permanent:
                ResourceCachePool.PermanentDictionary.Add(resourceId, new CacheBlock(resourceId, allocPtr, priority));
                break;
            }
        }
コード例 #7
0
        /// <summary>
        /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
            {
                return(false);
            }

            CacheBlock <TKey, TValue> other = (CacheBlock <TKey, TValue>)obj;

            return(this.Key.Equals(other.Key) && this.Value.Equals(other.Value));
        }
コード例 #8
0
        /// <summary>
        /// Writes data to the stream at the current location.
        /// </summary>
        /// <param name="buffer">The data to write</param>
        /// <param name="offset">The first byte to write from buffer</param>
        /// <param name="count">The number of bytes to write</param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            CheckDisposed();

            _stats.TotalWritesIn++;

            long startPos = _position;

            int  blockSize  = _settings.BlockSize;
            long firstBlock = _position / blockSize;
            long endBlock   = Utilities.Ceil(Math.Min(_position + count, Length), blockSize);
            int  numBlocks  = (int)(endBlock - firstBlock);

            try
            {
                _wrappedStream.Position = _position;
                _wrappedStream.Write(buffer, offset, count);
            }
            catch
            {
                InvalidateBlocks(firstBlock, numBlocks);
                throw;
            }


            int offsetInNextBlock = (int)(_position % blockSize);

            if (offsetInNextBlock != 0)
            {
                _stats.UnalignedWritesIn++;
            }


            // For each block touched, if it's cached, update it
            int bytesProcessed = 0;

            for (int i = 0; i < numBlocks; ++i)
            {
                int bufferPos      = offset + bytesProcessed;
                int bytesThisBlock = Math.Min(count - bufferPos, blockSize - offsetInNextBlock);

                if (_blocks.ContainsKey(firstBlock + i))
                {
                    CacheBlock block = _blocks[firstBlock + i];
                    Array.Copy(buffer, bufferPos, block.Data, offsetInNextBlock, bytesThisBlock);
                }

                offsetInNextBlock = 0;
                bytesProcessed   += bytesThisBlock;
            }

            _position += count;
        }
コード例 #9
0
ファイル: CachedStorage.cs プロジェクト: tckm1977/LibHac
        private void FlushBlock(CacheBlock block)
        {
            if (!block.Dirty)
            {
                return;
            }

            long offset = block.Index * BlockSize;

            BaseStorage.Write(block.Buffer.AsSpan(0, block.Length), offset);
            block.Dirty = false;
        }
コード例 #10
0
 private void Evict()
 {
     if (lfucache.Count != Capacity)
     {
         return;
     }
     lfucache.Remove(least.Key);
     if (least.Next != null)
     {
         least.Next.Prev = null;
     }
     least = least.Next;
 }
コード例 #11
0
        private void Add(CacheBlock block)
        {
            lfucache.Add(block.Key, block);

            block.Frequency = 1;
            block.Next      = least;
            if (least != null)
            {
                least.Prev = block;
            }
            least = block;

            Elevate(least);
        }
コード例 #12
0
 private void InvalidateBlocks(long firstBlock, int numBlocks)
 {
     for (long i = firstBlock; i < (firstBlock + numBlocks); ++i)
     {
         if (_blocks.ContainsKey(i))
         {
             CacheBlock block = _blocks[i];
             _blocks.Remove(i);
             _lru.Remove(block);
             _freeBlocks.Add(block);
             _stats.FreeReadBlocks++;
         }
     }
 }
コード例 #13
0
ファイル: CachedStorage.cs プロジェクト: tckm1977/LibHac
        private void ReadBlock(CacheBlock block, long index)
        {
            long offset = index * BlockSize;
            int  length = BlockSize;

            if (Length != -1)
            {
                length = (int)Math.Min(Length - offset, length);
            }

            BaseStorage.Read(block.Buffer.AsSpan(0, length), offset);
            block.Length = length;
            block.Index  = index;
            block.Dirty  = false;
        }
コード例 #14
0
 public static IntPtr Alloc(int size)
 {
     lock (memoryBlocks)
     {
         if (((busyBlocks >= maximumCacheSize) || (size > maxSizeToCache)) || (size < minSizeToCache))
         {
             return(Marshal.AllocHGlobal(size));
         }
         if (currentCacheSize == busyBlocks)
         {
             IntPtr memoryBlock = Marshal.AllocHGlobal(size);
             memoryBlocks.Add(new CacheBlock(memoryBlock, size));
             busyBlocks++;
             currentCacheSize++;
             cachedMemory += size;
             return(memoryBlock);
         }
         for (int i = 0; i < currentCacheSize; i++)
         {
             CacheBlock block = memoryBlocks[i];
             if (block.Free && (block.Size >= size))
             {
                 block.Free = false;
                 busyBlocks++;
                 return(block.MemoryBlock);
             }
         }
         for (int j = 0; j < currentCacheSize; j++)
         {
             CacheBlock block2 = memoryBlocks[j];
             if (block2.Free)
             {
                 Marshal.FreeHGlobal(block2.MemoryBlock);
                 memoryBlocks.RemoveAt(j);
                 currentCacheSize--;
                 cachedMemory -= block2.Size;
                 IntPtr ptr2 = Marshal.AllocHGlobal(size);
                 memoryBlocks.Add(new CacheBlock(ptr2, size));
                 busyBlocks++;
                 currentCacheSize++;
                 cachedMemory += size;
                 return(ptr2);
             }
         }
         return(IntPtr.Zero);
     }
 }
コード例 #15
0
ファイル: CachedStorage.cs プロジェクト: garoxas/LibHac
        public CachedStorage(IStorage baseStorage, int blockSize, int cacheSize, bool leaveOpen)
        {
            BaseStorage = baseStorage;
            BlockSize   = blockSize;
            LeaveOpen   = leaveOpen;

            BaseStorage.GetSize(out long baseSize).ThrowIfFailure();
            Length = baseSize;

            for (int i = 0; i < cacheSize; i++)
            {
                var block = new CacheBlock {
                    Buffer = new byte[blockSize], Index = -1
                };
                Blocks.AddLast(block);
            }
        }
コード例 #16
0
        public Cache(int cacheSize)
        {
            CacheSize = cacheSize;

            // Fill up the block array with null values
            Blocks = new CacheBlock <T> [cacheSize];
            for (var i = 0; i < cacheSize; i++)
            {
                var words = new T[Constants.WordsInBlock];
                for (var j = 0; j < Constants.WordsInBlock; j++)
                {
                    words[j] = default(T);
                }

                Blocks[i] = new CacheBlock <T>(words);
            }
        }
コード例 #17
0
        private void Add(CacheBlock block)
        {
            lrucache.Add(block.Key, block);

            if (latest != null)
            {
                block.Prev  = latest;
                block.Next  = latest.Next;
                latest.Next = block;
                latest      = block;
            }
            else
            {
                latest      = block;
                latest.Next = latest;
            }
        }
コード例 #18
0
        private CacheBlock GetBlock(long blockIndex)
        {
            if (BlockDict.TryGetValue(blockIndex, out LinkedListNode <CacheBlock> node))
            {
                if (Blocks.First != node)
                {
                    Blocks.Remove(node);
                    Blocks.AddFirst(node);
                }

                // If a supposedly active block is null, something's really wrong
                if (node is null)
                {
                    throw new NullReferenceException("CachedStorage cache block is unexpectedly null.");
                }

                return(node.Value);
            }

            // An inactive node shouldn't be null, but we'll fix it if it is anyway
            node = Blocks.Last ??
                   new LinkedListNode <CacheBlock>(new CacheBlock {
                Buffer = new byte[BlockSize], Index = -1
            });

            FlushBlock(node.Value);

            CacheBlock block = node.Value;

            Blocks.RemoveLast();

            if (block.Index != -1)
            {
                BlockDict.Remove(block.Index);
            }

            FlushBlock(block);
            ReadBlock(block, blockIndex);

            Blocks.AddFirst(node);
            BlockDict.Add(blockIndex, node);

            return(block);
        }
コード例 #19
0
ファイル: CachedStorage.cs プロジェクト: gabest11/nsZip
        public CachedStorage(IStorage baseStorage, int blockSize, int cacheSize, bool leaveOpen)
        {
            BaseStorage = baseStorage;
            BlockSize   = blockSize;
            _length     = BaseStorage.GetSize();

            if (!leaveOpen)
            {
                ToDispose.Add(BaseStorage);
            }

            for (int i = 0; i < cacheSize; i++)
            {
                var block = new CacheBlock {
                    Buffer = new byte[blockSize], Index = -1
                };
                Blocks.AddLast(block);
            }
        }
コード例 #20
0
ファイル: CachedStorage.cs プロジェクト: tiliarou/LibHac
        public CachedStorage(IStorage baseStorage, int blockSize, int cacheSize, bool leaveOpen)
        {
            BaseStorage = baseStorage;
            BlockSize   = blockSize;
            Length      = BaseStorage.Length;

            if (!leaveOpen)
            {
                ToDispose.Add(BaseStorage);
            }

            for (int i = 0; i < cacheSize; i++)
            {
                var block = new CacheBlock {
                    Buffer = ArrayPool <byte> .Shared.Rent(blockSize)
                };
                Blocks.AddLast(block);
            }
        }
コード例 #21
0
        public void Put(int key, int value)
        {
            CacheBlock block;

            // no such block, try add it
            if (!lrucache.TryGetValue(key, out block))
            {
                Evict();

                Add(block = new CacheBlock
                {
                    Key   = key,
                    Value = value
                });
            }
            else
            {
                block.Value = value;
                Elevate(block);
            }
        }
コード例 #22
0
ファイル: MarkdownResponse.cs プロジェクト: sgww/cozy
        public MarkdownResponse(string path)
        {
            StatusCode  = HttpStatusCode.OK;
            ContentType = "text/html; charset=utf-8";

            Contents = stream =>
            {
                using (var writer = new StreamWriter(stream))
                {
                    FileInfo fi    = new FileInfo(path);
                    var      cache = CacheManager.Instance.MarkdownCache.GetCache(path);

                    if (cache != null && cache.IsEffective(fi))
                    {
                        // Using Cache
                        writer.Write(cache.Data);
                        return;
                    }

                    // Cache miss
                    string context = null;
                    using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                    {
                        using (var reader = new StreamReader(fs, Encoding.UTF8))
                        {
                            context = CommonMarkConverter.Convert(reader.ReadToEnd());
                            writer.Write(context);
                        }
                    }

                    // cache update
                    if (cache == null)
                    {
                        cache = new CacheBlock();
                    }
                    cache.Update(context, fi);
                    CacheManager.Instance.MarkdownCache.Update(path, cache);
                }
            };
        }
コード例 #23
0
        private void Elevate(CacheBlock block)
        {
            if (block == latest)
            {
                return;
            }

            block.Next.Prev = block.Prev;
            if (block.Prev != null)
            {
                block.Prev.Next = block.Next;
            }

            var oldest = latest.Next != block ? latest.Next : block.Next;

            oldest.Prev = null;

            block.Next  = oldest;
            block.Prev  = latest;
            latest.Next = block;
            latest      = block;
        }
コード例 #24
0
ファイル: CachedStorage.cs プロジェクト: garoxas/LibHac
        private CacheBlock GetBlock(long blockIndex)
        {
            if (BlockDict.TryGetValue(blockIndex, out LinkedListNode <CacheBlock> node))
            {
                if (Blocks.First != node)
                {
                    Blocks.Remove(node);
                    Blocks.AddFirst(node);
                }

                return(node !.Value);
            }

            // An inactive node shouldn't be null, but we'll fix it if it is anyway
            node = Blocks.Last ??
                   new LinkedListNode <CacheBlock>(new CacheBlock {
                Buffer = new byte[BlockSize], Index = -1
            });

            FlushBlock(node.Value);

            CacheBlock block = node.Value;

            Blocks.RemoveLast();

            if (block.Index != -1)
            {
                BlockDict.Remove(block.Index);
            }

            FlushBlock(block);
            ReadBlock(block, blockIndex);

            Blocks.AddFirst(node);
            BlockDict.Add(blockIndex, node);

            return(block);
        }
コード例 #25
0
ファイル: CachedStorage.cs プロジェクト: tckm1977/LibHac
        protected override void ReadImpl(Span <byte> destination, long offset)
        {
            long remaining = destination.Length;
            long inOffset  = offset;
            int  outOffset = 0;

            lock (Blocks)
            {
                while (remaining > 0)
                {
                    long       blockIndex = inOffset / BlockSize;
                    int        blockPos   = (int)(inOffset % BlockSize);
                    CacheBlock block      = GetBlock(blockIndex);

                    int bytesToRead = (int)Math.Min(remaining, BlockSize - blockPos);

                    block.Buffer.AsSpan(blockPos, bytesToRead).CopyTo(destination.Slice(outOffset));

                    outOffset += bytesToRead;
                    inOffset  += bytesToRead;
                    remaining -= bytesToRead;
                }
            }
        }
コード例 #26
0
 private CacheBlock GetFreeBlock()
 {
     if (_freeBlocks.Count > 0)
     {
         int        idx   = _freeBlocks.Count - 1;
         CacheBlock block = _freeBlocks[idx];
         _freeBlocks.RemoveAt(idx);
         _stats.FreeReadBlocks--;
         return(block);
     }
     else if (_blocksCreated < _totalBlocks)
     {
         _blocksCreated++;
         _stats.FreeReadBlocks--;
         return(new CacheBlock(_settings.BlockSize));
     }
     else
     {
         CacheBlock block = _lru.Last.Value;
         _lru.RemoveLast();
         _blocks.Remove(block.Block);
         return(block);
     }
 }
コード例 #27
0
 private void StoreBlock(CacheBlock block)
 {
     _blocks[block.Block] = block;
     _lru.AddFirst(block);
 }
コード例 #28
0
 public LFUCache(int capacity)
 {
     Capacity = capacity < 0 ? 0 : capacity;
     lfucache = new System.Collections.Generic.Dictionary <int, CacheBlock>(Capacity);
     least    = null;
 }
コード例 #29
0
            public T LoadData(int index)
            {
                //Debug.WriteLine(string.Format("LoadData {0}",index));
                ++_cacheRequest;
                T returnValue;

                var cacheBlockNode    = _cacheBlock.First;
                int indexInCacheBlock = -1;

                while (cacheBlockNode != null)
                {
                    if (cacheBlockNode.Value.Contains(index, out indexInCacheBlock))
                    {
                        break;
                    }
                    cacheBlockNode = cacheBlockNode.Next;
                }

                if (cacheBlockNode == null)
                {
                    ++_cacheMisses;
                    CacheBlock cacheBlock;
                    if (_cacheBlock.Count < NumCacheBlocks)
                    {
                        cacheBlockNode = new LinkedListNode <CacheBlock>(cacheBlock = new CacheBlock(NumItemsPerCacheBlock));
                    }
                    else
                    {
                        cacheBlockNode = _cacheBlock.Last;
                        _cacheBlock.RemoveLast();
                        cacheBlock = cacheBlockNode.Value;
                    }
                    indexInCacheBlock = index % cacheBlock.Data.Length;

                    //request to fill the cacheblock
                    cacheBlock.StartIndex = index - indexInCacheBlock;
                    int count = InternalLoad(cacheBlock.StartIndex, cacheBlock.Data);
                    cacheBlock.EndIndex = Math.Min(count, cacheBlock.StartIndex + cacheBlock.Data.Length) - 1;
                    if (count != _count /*collection has changed in the meantime*/)
                    {
                        bool firstTime = _count == UninitializedCount;
                        // update the count [HÄÄ??->] unless it was undefined
                        _count = count;

                        // signal that our collection has changed, if this is not the first time aroud
                        // failure to check for this will give a nullreferenceexception on collectionchanged
                        if (!firstTime)
                        {
                            _list.NotifyCollectionChanged();
                        } /*DEPRECATED*/                                                                        //TODO remove list access

                        // clear the cache: the only block left is the one we're holding
                        _cacheBlock.Clear();
                    }
                    _cacheBlock.AddFirst(cacheBlockNode);

                    // if the index is outside the bounds of the new count, return nothing
                    if (indexInCacheBlock > cacheBlock.EndIndex)
                    {
                        returnValue = null;
                    }
                    else
                    {
                        returnValue = cacheBlock.Data[indexInCacheBlock];
                    }
                    if (/*DEBUG*/ returnValue == null)
                    {
                        Debug.WriteLine("returning null!");
                    }
                }
                else
                {
                    // move the block to the front of the cache if it's not already there
                    if (cacheBlockNode != _cacheBlock.First)
                    {
                        _cacheBlock.Remove(cacheBlockNode);
                        _cacheBlock.AddFirst(cacheBlockNode);
                    }
                    returnValue = cacheBlockNode.Value.Data[indexInCacheBlock];
                    if (/*DEBUG*/ returnValue == null)
                    {
                        Debug.WriteLine("returning null!");
                    }
                }
                if (/*DEBUG*/ returnValue == null)
                {
                    Debug.WriteLine("returning null!");
                }
                return(returnValue);
            }
コード例 #30
0
        /// <summary>
        /// Reads data from the stream.
        /// </summary>
        /// <param name="buffer">The buffer to fill</param>
        /// <param name="offset">The buffer offset to start from</param>
        /// <param name="count">The number of bytes to read</param>
        /// <returns>The number of bytes read</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            CheckDisposed();

            long readStartPos = _position;

            if (_position >= Length)
            {
                if (_atEof)
                {
                    throw new IOException("Attempt to read beyond end of stream");
                }
                else
                {
                    _atEof = true;
                    return(0);
                }
            }

            _stats.TotalReadsIn++;

            if (count > _settings.LargeReadSize)
            {
                _stats.LargeReadsIn++;
                _stats.TotalReadsOut++;
                _wrappedStream.Position = _position;
                int numRead = _wrappedStream.Read(buffer, offset, count);
                _position = _wrappedStream.Position;

                if (_position >= Length)
                {
                    _atEof = true;
                }

                return(numRead);
            }

            int  totalBytesRead       = 0;
            bool servicedFromCache    = false;
            bool servicedOutsideCache = false;
            int  blockSize            = _settings.BlockSize;

            long firstBlock        = _position / blockSize;
            int  offsetInNextBlock = (int)(_position % blockSize);
            long endBlock          = Utilities.Ceil(Math.Min(_position + count, Length), blockSize);
            int  numBlocks         = (int)(endBlock - firstBlock);

            if (offsetInNextBlock != 0)
            {
                _stats.UnalignedReadsIn++;
            }

            int blocksRead = 0;

            while (blocksRead < numBlocks)
            {
                // Read from the cache as much as possible
                while (blocksRead < numBlocks && _blocks.ContainsKey(firstBlock + blocksRead))
                {
                    CacheBlock block       = _blocks[firstBlock + blocksRead];
                    int        bytesToRead = Math.Min(count - totalBytesRead, block.Data.Length - offsetInNextBlock);

                    Array.Copy(block.Data, offsetInNextBlock, buffer, offset + totalBytesRead, bytesToRead);
                    offsetInNextBlock = 0;
                    totalBytesRead   += bytesToRead;
                    _position        += bytesToRead;
                    blocksRead++;

                    servicedFromCache = true;
                }

                // Now handle a sequence of (one or more) blocks that are not cached
                if (blocksRead < numBlocks && !_blocks.ContainsKey(firstBlock + blocksRead))
                {
                    servicedOutsideCache = true;

                    // Figure out how many blocks to read from the wrapped stream
                    int blocksToRead = 0;
                    while (blocksRead + blocksToRead < numBlocks &&
                           blocksToRead < _blocksInReadBuffer &&
                           !_blocks.ContainsKey(firstBlock + blocksRead + blocksToRead))
                    {
                        ++blocksToRead;
                    }

                    // Allow for the end of the stream not being block-aligned
                    long readPosition = (firstBlock + blocksRead) * (long)blockSize;
                    int  bytesToRead  = (int)Math.Min(blocksToRead * blockSize, Length - readPosition);

                    // Do the read
                    _stats.TotalReadsOut++;
                    _wrappedStream.Position = readPosition;
                    int bytesRead = Utilities.ReadFully(_wrappedStream, _readBuffer, 0, bytesToRead);
                    if (bytesRead != bytesToRead)
                    {
                        throw new IOException("Short read before end of stream");
                    }

                    // Cache the read blocks
                    for (int i = 0; i < blocksToRead; ++i)
                    {
                        int        copyBytes = Math.Min(blockSize, bytesToRead - i * blockSize);
                        CacheBlock block     = GetFreeBlock();
                        block.Block = firstBlock + blocksRead + i;
                        Array.Copy(_readBuffer, i * blockSize, block.Data, 0, copyBytes);

                        if (copyBytes < blockSize)
                        {
                            Array.Clear(_readBuffer, copyBytes, blockSize - copyBytes);
                        }

                        StoreBlock(block);
                    }
                    blocksRead += blocksToRead;

                    // Propogate the data onto the caller
                    int bytesToCopy = Math.Min(count - totalBytesRead, bytesRead - offsetInNextBlock);
                    Array.Copy(_readBuffer, offsetInNextBlock, buffer, offset + totalBytesRead, bytesToCopy);
                    totalBytesRead   += bytesToCopy;
                    _position        += bytesToCopy;
                    offsetInNextBlock = 0;
                }
            }

            if (_position >= Length)
            {
                _atEof = true;
            }

            if (servicedFromCache)
            {
                _stats.ReadCacheHits++;
            }
            if (servicedOutsideCache)
            {
                _stats.ReadCacheMisses++;
            }

            return(totalBytesRead);
        }