// postcondition: bufferedLength >= _currentIndex + min(readAhead, AmountLeft(_stream)) private void Buffer(int readAhead) { var readAheadTo = _currentIndex + readAhead; if (readAheadTo >= _bufferedCount) { // we're about to read past the end of the current chunk. Pull a new chunk from the stream var keepSeenLength = _bookmarks.Count > 0 ? Location - _bookmarks[0] : 0; var keepFrom = _currentIndex - keepSeenLength; var keepLength = _bufferedCount - keepFrom; var amountToRead = Math.Max(_bufferChunkSize, readAheadTo - keepFrom); var newBufferLength = _bufferedCount + amountToRead; // _currentIndex // | // | _bufferedCount // keepFrom | | // | | | readAheadTo // | | | | // abcdefghijklmnopqrstuvwxyz // readAhead |-----------| // keepSeenLength |------| // keepLength |-------------| // amountToRead |----| // newBufferLength |------------------| for (var i = 0; i < keepFrom; i++) { _bufferStartSourcePos = _posCalculator(_buffer[i], _bufferStartSourcePos); } if (newBufferLength > _buffer.Length) { // grow the buffer var newBuffer = ArrayPool <TToken> .Shared.Rent(Math.Max(newBufferLength, _buffer.Length * 2)); Array.Copy(_buffer, keepFrom, newBuffer, 0, keepLength); ArrayPool <TToken> .Shared.Return(_buffer); _buffer = newBuffer; } else if (keepFrom != 0 && keepLength != 0) { // move the buffer's contents to the start // todo: find out how expensive this Copy tends to be. // Could prevent it by using a ring buffer, but might make reads slower Array.Copy(_buffer, keepFrom, _buffer, 0, keepLength); } _bufferStartLocation += keepFrom; _currentIndex = keepSeenLength; _bufferedCount = keepLength; _bufferedCount += _stream.ReadInto(_buffer, _bufferedCount, amountToRead); } }