This is a small note I wrote in 1997 while implementing a ring buffer. I couldn't find any good tutorial with suitably detailed instructions (e.g. source code in C) from the web back then - hope this fixes the lack. :)
LP = Load Pointer =
CP = Consume Pointer =
Loaded, not usable area (always 1 byte) =
Loaded and usable area =
RBE = ring buffer end address (first byte which may not be consumed)
RBS = ring buffer start address (first byte which MAY be consumed)
The principle: CP never equals LP: there's always a 1 byte gap.
(Another way is to have an extra variable to tell wether the buffer is full or empty
when LP and CP point to the same address. This method is used in the sample code below.)
The first byte of the stream must be read in to the gap between CP and LP before starting the actual streaming.
Consume pointer in the beginning (CP=RBS) |
|
Most usual case (CP>RBS, LP<EBS) |
|
Wrap around | |
Buffer empty (LP=CP+1) | |
Buffer full (CP=LP+1) |
if (CP==RBE && LP!=RBS) CP = RBS if (LP==RBE && CP!=RBS) LP = RBS if (LP>CP) { maxLoadBytes = RBE - LP maxConsumeBytes = LP - CP - 1 } else { maxLoadBytes = CP - LP -1 maxConsumeBytes = RBE - CP // Or if the consumer knows how to wrap around: // maxConsumeBytes = (RBE - CP) + (LP - RBS) - 1...and after consuming or loading:
LP += maxLoadBytes CP += maxConsumeBytes
Here's a small C++ ring buffer class:
class Ring_Buffer { public: Ring_Buffer( void* buffer, unsigned int buffer_size ) { m_buff = (unsigned char*)buffer; m_buff_end = m_buff + buffer_size; m_load_ptr = m_consume_ptr = m_buff; m_max_load = buffer_size; m_max_consume = m_data_in_buffer = 0; Update_State(); } // Try to add data to the buffer. After the call, 'len' contains // the amount of bytes actually buffered. void Buffer_Data( const void* data, unsigned int& len ) { if ( len > (unsigned int)m_max_load ) len = (unsigned int)m_max_load; memcpy( m_load_ptr, data, len ); m_load_ptr += len; m_data_in_buffer += len; Update_State(); } // Request 'len' bytes from the buffer. After the call, // 'len' contains the amount of bytes actually copied. void Get_Data( void* outData, unsigned int& len ) { if ( len > (unsigned int)m_max_consume ) len = (unsigned int)m_max_consume; memcpy( outData, m_consume_ptr, len ); m_consume_ptr += len; m_data_in_buffer -= len; Update_State(); } // Tries to skip len bytes. After the call, // 'len' contains the realized skip. void SkipData( unsigned int& len ) { unsigned int requestedSkip = len; for ( int i=0; i<2; ++i ) // This may wrap so try it twice { int skip = (int)len; if ( skip > m_max_consume ) skip = m_max_consume; m_consume_ptr += skip; m_data_in_buffer -= skip; len -= skip; Update_State(); } len = requestedSkip - len; } // The amount of data the buffer can currently receive on one Buffer_Data() call. inline unsigned int Free_Space() { return (unsigned int)m_max_load; } // The total amount of data in the buffer. Note that it may not be continuous: you may need // two successive calls to Get_Data() to get it all. inline unsigned int Buffered_Bytes() { return (unsigned int)m_data_in_buffer; } private: void Update_State() { if (m_consume_ptr == m_buff_end) m_consume_ptr = m_buff; if (m_load_ptr == m_buff_end) m_load_ptr = m_buff; if (m_load_ptr == m_consume_ptr) { if ( m_data_in_buffer > 0 ) { m_max_load = 0; m_max_consume = m_buff_end - m_consume_ptr; } else { m_max_load = m_buff_end - m_load_ptr; m_max_consume = 0; } } else if ( m_load_ptr > m_consume_ptr ) { m_max_load = m_buff_end - m_load_ptr; m_max_consume = m_load_ptr - m_consume_ptr; } else { m_max_load = m_consume_ptr - m_load_ptr; m_max_consume = m_buff_end - m_consume_ptr; } } unsigned char *m_load_ptr, *m_consume_ptr; unsigned char *m_buff_end; unsigned char *m_buff; int m_max_load, m_max_consume, m_data_in_buffer; };