From eb86c76b9fc074cf0ddf0f534734fec08802dd54 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Tue, 16 Mar 2004 03:06:10 +0000 Subject: [PATCH] Make CircBuf are lock-free, threadsafe buffer. (We don't actually take advantage of this yet.) --- stepmania/src/RageSound.cpp | 6 +- stepmania/src/RageUtil_CircularBuffer.h | 221 ++++++++++++++++++++---- 2 files changed, 188 insertions(+), 39 deletions(-) diff --git a/stepmania/src/RageSound.cpp b/stepmania/src/RageSound.cpp index 30d2eecd13..287ed05c40 100644 --- a/stepmania/src/RageSound.cpp +++ b/stepmania/src/RageSound.cpp @@ -203,7 +203,7 @@ void RageSound::Update(float delta) /* Return the number of bytes available in the input buffer. */ int RageSound::Bytes_Available() const { - return databuf.size(); + return databuf.num_readable(); } @@ -277,7 +277,7 @@ int RageSound::FillBuf(int bytes) while(bytes > 0) { - if(read_block_size > databuf.capacity() - databuf.size()) + if(read_block_size > databuf.num_writable()) break; /* full */ char inbuf[10240]; @@ -347,7 +347,7 @@ int RageSound::GetData(char *buffer, int size) } else { /* Feed data out of our streaming buffer. */ ASSERT(Sample); - got = min(int(databuf.size()), size); + got = min(int(databuf.num_readable()), size); if(buffer) databuf.read(buffer, got); } diff --git a/stepmania/src/RageUtil_CircularBuffer.h b/stepmania/src/RageUtil_CircularBuffer.h index 4926e50b6e..123aec6be8 100644 --- a/stepmania/src/RageUtil_CircularBuffer.h +++ b/stepmania/src/RageUtil_CircularBuffer.h @@ -1,67 +1,216 @@ #ifndef RAGE_UTIL_CIRCULAR_BUFFER #define RAGE_UTIL_CIRCULAR_BUFFER +/* Lock-free circular buffer. This should be threadsafe if one thread is reading + * and another is writing. */ template class CircBuf { - basic_string buf; - unsigned cnt, start; - + T *buf; + /* read_pos is the position data is read from; write_pos is the position + * data is written to. If read_pos == write_pos, the buffer is empty. + * + * There will always be at least one position empty, as a completely full + * buffer (read_pos == write_pos) is indistinguishable from an empty buffer. + * + * Invariants: read_pos < size, write_pos < size. */ + unsigned size; + + /* These are volatile to prevent reads and writes to them from being optimized. */ + volatile unsigned read_pos, write_pos; + public: - CircBuf() { clear(); } - unsigned size() const { return cnt; } - unsigned capacity() const { return buf.size(); } + CircBuf() + { + buf = NULL; + clear(); + } + + ~CircBuf() + { + delete[] buf; + } + + /* Return the number of elements available to read. */ + unsigned num_readable() const + { + const int rpos = read_pos; + const int wpos = write_pos; + if( rpos < wpos ) + /* The buffer looks like "eeeeDDDDeeee" (e = empty, D = data). */ + return wpos - rpos; + else if( rpos > wpos ) + /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */ + return size - (rpos - wpos); + else // if( rpos == wpos ) + /* The buffer looks like "eeeeeeeeeeee" (e = empty, D = data). */ + return 0; + } + + /* Return the number of elements writable. Note that there must always + * be one */ + unsigned num_writable() const + { + const int rpos = read_pos; + const int wpos = write_pos; + + int ret; + if( rpos < wpos ) + /* The buffer looks like "eeeeDDDDeeee" (e = empty, D = data). */ + ret = size - (wpos - rpos); + else if( rpos > wpos ) + /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */ + ret = rpos - wpos; + else // if( rpos == wpos ) + /* The buffer looks like "eeeeeeeeeeee" (e = empty, D = data). */ + ret = size; + + /* Subtract one, to account for the element that we never fill. */ + return ret - 1; + } + + unsigned capacity() const { return size; } void reserve( unsigned n ) { + /* Reserve an extra byte. We'll never fill more than n bytes; the extra + * byte is to guarantee that read_pos != write_pos when the buffer is full, + * since that would be ambiguous with an empty buffer. */ clear(); - buf.erase(); - buf.insert( buf.end(), n, 0 ); + delete[] buf; + buf = new T[n+1]; + size = n+1; } void clear() { - cnt = start = 0; + read_pos = write_pos = 0; } - void write( const T *buffer, unsigned buffer_size ) + /* Indicate that n elements have been written. */ + void advance_write_pointer( int n ) { - ASSERT( size() + buffer_size <= capacity() ); /* overflow */ + write_pos = (write_pos + n) % size; + } + + /* Indicate that n elements have been read. */ + void advance_read_pointer( int n ) + { + read_pos = (read_pos + n) % size; + } + + void get_write_pointers( T *pPointers[2], unsigned pSizes[2] ) + { + const int rpos = read_pos; + const int wpos = write_pos; - while( buffer_size ) + if( rpos <= wpos ) { - unsigned write_pos = start + size(); - if( write_pos >= buf.size() ) - write_pos -= buf.size(); - - const int cpy = int(min(buffer_size, buf.size() - write_pos)); - buf.replace( write_pos, cpy, buffer, cpy ); + /* The buffer looks like "eeeeDDDDeeee" or "eeeeeeeeeeee" (e = empty, D = data). */ + pPointers[0] = buf+wpos; + pPointers[1] = buf; - cnt += cpy; + pSizes[0] = size - wpos; + pSizes[1] = rpos; + } + else if( rpos > wpos ) + { + /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */ + pPointers[0] = buf+wpos; + pPointers[1] = NULL; - buffer += cpy; - buffer_size -= cpy; + pSizes[0] = rpos - wpos; + pSizes[1] = 0; + } + + /* Subtract one, to account for the element that we never fill. */ + if( pSizes[1] ) + --pSizes[1]; + else + --pSizes[0]; + } + + void get_read_pointers( T *pPointers[2], unsigned pSizes[2] ) + { + const int rpos = read_pos; + const int wpos = write_pos; + + if( rpos < wpos ) + { + /* The buffer looks like "eeeeDDDDeeee" (e = empty, D = data). */ + pPointers[0] = buf+rpos; + pPointers[1] = NULL; + + pSizes[0] = wpos - rpos; + pSizes[1] = 0; + } + else if( rpos > wpos ) + { + /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */ + pPointers[0] = buf+rpos; + pPointers[1] = buf; + + pSizes[0] = size - rpos; + pSizes[1] = wpos; + } + else + { + /* The buffer looks like "eeeeeeeeeeee" (e = empty, D = data). */ + pPointers[0] = NULL; + pPointers[1] = NULL; + + pSizes[0] = 0; + pSizes[1] = 0; } } - - void read( T *buffer, unsigned buffer_size ) + + /* Write buffer_size elements from buffer, and advance the write pointer. If + * the data will not fit entirely, the write pointer will be unchanged + * and false will be returned. */ + bool write( const T *buffer, unsigned buffer_size ) { - ASSERT( size() >= buffer_size ); /* underflow */ + T *p[2]; + unsigned sizes[2]; + get_write_pointers( p, sizes ); + + if( buffer_size > sizes[0] + sizes[1] ) + return false; - while( buffer_size ) - { - const unsigned total = static_cast(min(buf.size() - start, size())); - const unsigned cpy = min( buffer_size, total ); - buf.copy( buffer, cpy, start ); + const int from_first = min( buffer_size, sizes[0] ); + memcpy( p[0], buffer, from_first*sizeof(T) ); + if( buffer_size > sizes[0] ) + memcpy( p[1], buffer+from_first, max(buffer_size-sizes[0], 0u)*sizeof(T) ); - start += cpy; - if( start == buf.size() ) - start = 0; - cnt -= cpy; + advance_write_pointer( buffer_size ); - buffer += cpy; - buffer_size -= cpy; - } + return true; + } + + /* Read buffer_size elements from buffer, and advance the read pointer. If + * the buffer can not be filled completely, the read pointer will be unchanged + * and false will be returned. */ + bool read( T *buffer, unsigned buffer_size ) + { + T *p[2]; + unsigned sizes[2]; + get_read_pointers( p, sizes ); + + if( buffer_size > sizes[0] + sizes[1] ) + return false; + + const int from_first = min( buffer_size, sizes[0] ); + memcpy( buffer, p[0], from_first*sizeof(T) ); + if( buffer_size > sizes[0] ) + memcpy( buffer+from_first, p[1], max(buffer_size-sizes[0], 0u)*sizeof(T) ); + + /* Set the data that we just read to 0xFF. This way, if we're passing pointesr + * through, we can tell if we accidentally get a stale pointer. */ + memset( p[0], 0xFF, from_first*sizeof(T) ); + if( buffer_size > sizes[0] ) + memset( p[1], 0xFF, max(buffer_size-sizes[0], 0u)*sizeof(T) ); + + advance_read_pointer( buffer_size ); + return true; } };