1 #include "api/internal/io/RollingBuffer_p.h"
2 using namespace BamTools;
3 using namespace BamTools::Internal;
5 #include <iostream> // for debug
13 // ------------------------------
14 // RollingBuffer implementation
15 // ------------------------------
17 RollingBuffer::RollingBuffer(size_t growth)
18 : m_bufferGrowth(growth)
20 // buffer always contains at least 1 (maybe empty) byte array
21 m_data.push_back( ByteArray() );
27 RollingBuffer::~RollingBuffer(void) { }
29 size_t RollingBuffer::BlockSize(void) const {
31 // if only one byte array in buffer <- needed?
32 if ( m_tailBufferIndex == 0 )
33 return m_tail - m_head;
35 // otherwise return remaining num bytes in first array
36 const ByteArray& first = m_data.front();
37 return first.Size() - m_head;
40 bool RollingBuffer::CanReadLine(void) const {
41 return IndexOf('\n') != string::npos;
44 void RollingBuffer::Chop(size_t n) {
47 if ( n > m_totalBufferSize )
48 m_totalBufferSize = 0;
50 m_totalBufferSize -= n;
52 // loop until target case hit
55 // if only one array, decrement tail
56 if ( m_tailBufferIndex == 0 ) {
59 // if all data chopped
60 if ( m_tail <= m_head ) {
67 // if there's room in last byte array to 'chop', just decrement tail
73 // otherwise we're going to overlap our internal byte arrays
74 // reduce our chop amount by the amount of data in the last byte array
77 // remove last byte array & set tail to it's end
80 m_tail = m_data.at(m_tailBufferIndex).Size();
83 // if buffer is now empty, reset state & clear up memory
88 void RollingBuffer::Clear(void) {
90 // remove all byte arrays (except first)
91 m_data.erase( m_data.begin()+1, m_data.end() );
93 // clear out first byte array
97 // reset index & size markers
100 m_tailBufferIndex = 0;
101 m_totalBufferSize = 0;
104 void RollingBuffer::Free(size_t n) {
106 // update buffer size
107 if ( n > m_totalBufferSize )
108 m_totalBufferSize = 0;
110 m_totalBufferSize -= n;
112 // loop until target case hit
115 const size_t blockSize = BlockSize();
117 // if there's room in current array
118 if ( n < blockSize ) {
120 // shift 'head' over @n bytes
123 // check for emptied, single byte array
124 if ( m_head == m_tail && m_tailBufferIndex == 0 ) {
132 // otherwise we need to check next byte array
133 // first update amount to remove
136 // special case - there was only 1 array
137 if ( m_data.size() == 1 ) {
138 if ( m_data.at(0).Size() != m_bufferGrowth )
139 m_data[0].Resize(m_bufferGrowth);
142 m_tailBufferIndex = 0;
146 // otherwise, remove first array and move to next iteration
152 // if buffer is now empty, reset state & clear up memory
157 size_t RollingBuffer::IndexOf(char c) const {
161 // iterate over byte arrays
162 const size_t numBuffers = m_data.size();
163 for ( size_t i = 0; i < numBuffers; ++i ) {
164 const ByteArray& current = m_data.at(i);
166 // if on first array, use head; else 0
167 const size_t start = ( (i==0) ? m_head : 0 );
169 // if on last array, set end; else use current byte array size
170 const size_t end = ( (i==m_tailBufferIndex) ? m_tail : current.Size());
172 // look through this iteration's byte array for @c
173 const char* p = current.ConstData()+start;
174 for ( size_t j = start; j < end; ++j ) {
185 bool RollingBuffer::IsEmpty(void) const {
186 return (m_tailBufferIndex == 0) && (m_tail == 0);
189 size_t RollingBuffer::Read(char* dest, size_t max) {
191 size_t bytesToRead = std::min(Size(), max);
192 size_t bytesReadSoFar = 0;
194 while ( bytesReadSoFar < bytesToRead ) {
195 const char* readPtr = ReadPointer();
196 size_t blockBytes = std::min( (bytesToRead - bytesReadSoFar), BlockSize() );
198 memcpy(dest+bytesReadSoFar, readPtr, blockBytes);
199 bytesReadSoFar += blockBytes;
203 return bytesReadSoFar;
206 size_t RollingBuffer::ReadLine(char* dest, size_t max) {
208 // if we can't read line or if max is 0
209 if ( !CanReadLine() || max == 0 )
212 // otherwise, read until we hit newline
213 size_t bytesReadSoFar = 0;
214 bool finished = false;
215 while ( !finished ) {
217 const size_t index = IndexOf('\n');
218 const char* readPtr = ReadPointer();
219 size_t bytesToRead = std::min( (index+1)-bytesReadSoFar, BlockSize() );
220 bytesToRead = std::min( bytesToRead, (max-1)-bytesReadSoFar );
221 memcpy(dest+bytesReadSoFar, readPtr, bytesToRead);
222 bytesReadSoFar += bytesToRead;
225 if ( !((bytesReadSoFar < index+1)&&(bytesReadSoFar < max-1)) )
229 // null terminate 'dest' & return numBytesRead
230 dest[bytesReadSoFar] = '\0';
231 return bytesReadSoFar;
234 string RollingBuffer::ReadLine(size_t max) {
239 size_t numBytesRead = 0;
241 // if max not provided, we need to read incrementally
245 // make sure we leave room for null terminator
250 result.Resize(std::min(max, result.Size()+m_bufferGrowth));
251 readResult = ReadLine(result.Data() + numBytesRead, result.Size() - numBytesRead);
252 if ( readResult > 0 || numBytesRead == 0 )
253 numBytesRead += readResult;
254 } while ( readResult == m_bufferGrowth && result[numBytesRead-1] != '\n');
257 // otherwise read line with provided max
258 else numBytesRead = ReadLine(result.Data(), result.Size());
260 // adjust byte array depending on numBytesRead
261 if ( numBytesRead == 0 )
264 result.Resize(numBytesRead);
266 // return string from byte array
267 return string(result.ConstData(), result.Size());
270 const char* RollingBuffer::ReadPointer(void) const {
272 // return null if empty buffer
273 if ( m_data.empty() )
276 // otherwise return pointer to current position
277 const ByteArray& first = m_data.front();
278 return first.ConstData() + m_head;
281 char* RollingBuffer::Reserve(size_t n) {
284 if ( m_totalBufferSize == 0 ) {
285 m_data[0].Resize( std::max(m_bufferGrowth, n) );
286 m_totalBufferSize += n;
288 return m_data[m_tailBufferIndex].Data();
291 // increment buffer's byte count
292 m_totalBufferSize += n;
294 // if buffer already contains enough space to fit @n more bytes
295 if ( (m_tail + n) <= m_data.at(m_tailBufferIndex).Size() ) {
297 // fetch write pointer at current 'tail', increment tail by @n & return
298 char* ptr = m_data[m_tailBufferIndex].Data() + m_tail;
303 // if last byte array isn't half full
304 if ( m_tail < m_data.at(m_tailBufferIndex).Size()/2 ) {
306 // we'll allow simple resize
307 m_data[m_tailBufferIndex].Resize(m_tail + n);
309 // fetch write pointer at current 'tail', increment tail by @n & return
310 char* ptr = m_data[m_tailBufferIndex].Data() + m_tail;
315 // otherwise, shrink last byte array to current used size
316 m_data[m_tailBufferIndex].Resize(m_tail);
318 // then append new byte array
319 m_data.push_back( ByteArray() );
321 m_data[m_tailBufferIndex].Resize( std::max(m_bufferGrowth, n) );
324 // return write-able pointer on new array
325 return m_data[m_tailBufferIndex].Data();
328 size_t RollingBuffer::Size(void) const {
329 return m_totalBufferSize;
332 void RollingBuffer::Write(const char* src, size_t n) {
333 char* writePtr = Reserve(n);
334 memcpy(writePtr, src, n);