// *************************************************************************** // RollingBuffer_p.cpp (c) 2011 Derek Barnett // Marth Lab, Department of Biology, Boston College // --------------------------------------------------------------------------- // Last modified: 8 December 2011 (DB) // --------------------------------------------------------------------------- // Provides a dynamic I/O FIFO byte queue, which removes bytes as they are // read from the front of the buffer and grows to accept bytes being written // to buffer end. // // implementation note: basically a 'smart' wrapper around 1..* ByteArrays // *************************************************************************** #include "api/internal/io/RollingBuffer_p.h" using namespace BamTools; using namespace BamTools::Internal; #include #include #include #include using namespace std; // ------------------------------ // RollingBuffer implementation // ------------------------------ RollingBuffer::RollingBuffer(size_t growth) : m_bufferGrowth(growth) { // buffer always contains at least 1 (maybe empty) byte array m_data.push_back( ByteArray() ); // set cleared state Clear(); } RollingBuffer::~RollingBuffer(void) { } size_t RollingBuffer::BlockSize(void) const { // if only one byte array in buffer <- needed? if ( m_tailBufferIndex == 0 ) return m_tail - m_head; // otherwise return remaining num bytes in first array const ByteArray& first = m_data.front(); return first.Size() - m_head; } bool RollingBuffer::CanReadLine(void) const { return IndexOf('\n') != string::npos; } void RollingBuffer::Chop(size_t n) { // update buffer size if ( n > m_totalBufferSize ) m_totalBufferSize = 0; else m_totalBufferSize -= n; // loop until target case hit for ( ; ; ) { // if only one array, decrement tail if ( m_tailBufferIndex == 0 ) { m_tail -= n; // if all data chopped if ( m_tail <= m_head ) { m_head = 0; m_tail = 0; } return; } // if there's room in last byte array to 'chop', just decrement tail if ( n <= m_tail ) { m_tail -= n; return; } // otherwise we're going to overlap our internal byte arrays // reduce our chop amount by the amount of data in the last byte array n -= m_tail; // remove last byte array & set tail to it's end m_data.pop_back(); --m_tailBufferIndex; m_tail = m_data.at(m_tailBufferIndex).Size(); } // if buffer is now empty, reset state & clear up memory if ( IsEmpty() ) Clear(); } void RollingBuffer::Clear(void) { // remove all byte arrays (except first) m_data.erase( m_data.begin()+1, m_data.end() ); // clear out first byte array m_data[0].Resize(0); m_data[0].Squeeze(); // reset index & size markers m_head = 0; m_tail = 0; m_tailBufferIndex = 0; m_totalBufferSize = 0; } void RollingBuffer::Free(size_t n) { // update buffer size if ( n > m_totalBufferSize ) m_totalBufferSize = 0; else m_totalBufferSize -= n; // loop until target case hit for ( ; ; ) { const size_t blockSize = BlockSize(); // if there's room in current array if ( n < blockSize ) { // shift 'head' over @n bytes m_head += n; // check for emptied, single byte array if ( m_head == m_tail && m_tailBufferIndex == 0 ) { m_head = 0; m_tail = 0; } break; } // otherwise we need to check next byte array // first update amount to remove n -= blockSize; // special case - there was only 1 array if ( m_data.size() == 1 ) { if ( m_data.at(0).Size() != m_bufferGrowth ) m_data[0].Resize(m_bufferGrowth); m_head = 0; m_tail = 0; m_tailBufferIndex = 0; break; } // otherwise, remove first array and move to next iteration m_data.pop_front(); --m_tailBufferIndex; m_head = 0; } // if buffer is now empty, reset state & clear up memory if ( IsEmpty() ) Clear(); } size_t RollingBuffer::IndexOf(char c) const { // skip processing if empty buffer if ( IsEmpty() ) return string::npos; size_t index(0); // iterate over byte arrays const size_t numBuffers = m_data.size(); for ( size_t i = 0; i < numBuffers; ++i ) { const ByteArray& current = m_data.at(i); // if on first array, use head; else 0 const size_t start = ( (i==0) ? m_head : 0 ); // if on last array, set end; else use current byte array size const size_t end = ( (i==m_tailBufferIndex) ? m_tail : current.Size()); // look through this iteration's byte array for @c const char* p = current.ConstData()+start; for ( size_t j = start; j < end; ++j ) { if ( *p++ == c ) return index; ++index; } } // no match found return string::npos; } bool RollingBuffer::IsEmpty(void) const { return (m_tailBufferIndex == 0) && (m_tail == 0); } size_t RollingBuffer::Read(char* dest, size_t max) { size_t bytesToRead = std::min(Size(), max); size_t bytesReadSoFar = 0; while ( bytesReadSoFar < bytesToRead ) { const char* readPtr = ReadPointer(); size_t blockBytes = std::min( (bytesToRead - bytesReadSoFar), BlockSize() ); if ( dest ) memcpy(dest+bytesReadSoFar, readPtr, blockBytes); bytesReadSoFar += blockBytes; Free(blockBytes); } return bytesReadSoFar; } size_t RollingBuffer::ReadLine(char* dest, size_t max) { // if we can't read line or if max is 0 if ( !CanReadLine() || max == 0 ) return 0; // otherwise, read until we hit newline size_t bytesReadSoFar = 0; bool finished = false; while ( !finished ) { const size_t index = IndexOf('\n'); const char* readPtr = ReadPointer(); size_t bytesToRead = std::min( (index+1)-bytesReadSoFar, BlockSize() ); bytesToRead = std::min( bytesToRead, (max-1)-bytesReadSoFar ); memcpy(dest+bytesReadSoFar, readPtr, bytesToRead); bytesReadSoFar += bytesToRead; Free(bytesToRead); if ( !((bytesReadSoFar < index+1)&&(bytesReadSoFar < max-1)) ) finished = true; } // null terminate 'dest' & return numBytesRead dest[bytesReadSoFar] = '\0'; return bytesReadSoFar; } const char* RollingBuffer::ReadPointer(void) const { // return null if empty buffer if ( m_data.empty() ) return 0; // otherwise return pointer to current position const ByteArray& first = m_data.front(); return first.ConstData() + m_head; } char* RollingBuffer::Reserve(size_t n) { // if empty buffer if ( m_totalBufferSize == 0 ) { m_data[0].Resize( std::max(m_bufferGrowth, n) ); m_totalBufferSize += n; m_tail = n; return m_data[m_tailBufferIndex].Data(); } // increment buffer's byte count m_totalBufferSize += n; // if buffer already contains enough space to fit @n more bytes if ( (m_tail + n) <= m_data.at(m_tailBufferIndex).Size() ) { // fetch write pointer at current 'tail', increment tail by @n & return char* ptr = m_data[m_tailBufferIndex].Data() + m_tail; m_tail += n; return ptr; } // if last byte array isn't half full if ( m_tail < m_data.at(m_tailBufferIndex).Size()/2 ) { // we'll allow simple resize m_data[m_tailBufferIndex].Resize(m_tail + n); // fetch write pointer at current 'tail', increment tail by @n & return char* ptr = m_data[m_tailBufferIndex].Data() + m_tail; m_tail += n; return ptr; } // otherwise, shrink last byte array to current used size m_data[m_tailBufferIndex].Resize(m_tail); // then append new byte array m_data.push_back( ByteArray() ); ++m_tailBufferIndex; m_data[m_tailBufferIndex].Resize( std::max(m_bufferGrowth, n) ); m_tail = n; // return write-able pointer on new array return m_data[m_tailBufferIndex].Data(); } size_t RollingBuffer::Size(void) const { return m_totalBufferSize; } void RollingBuffer::Write(const char* src, size_t n) { char* writePtr = Reserve(n); memcpy(writePtr, src, n); }