|
#include "buffers.hpp"
#include <algorithm>
#include <type_traits>
////////////////////////////////////////////////////////////////////////////////
//
// Helpers and private implementation details.
// -------------------------------------------
//
////////////////////////////////////////////////////////////////////////////////
namespace // anonymous
{
//************************************
// Method: InterlockedAdd
// FullName: InterlockedAdd<T>
// Access: global
//************************************
template <typename T>
void InterlockedAdd( T & t, long const addend )
{
assert( std::tr1::is_pod<T>::value );
assert( sizeof( T ) == sizeof( long ) );
// Even with full optimization, intrinsics on and optimize for speed
// the (MSVC 9.0) compiler chooses the Windows kernel implementation of
// InterlockedExchangeAdd so here the intrinsic version is forcibly
// called.
_InterlockedExchangeAdd( &reinterpret_cast<long &>( t ), addend );
}
//************************************
// Method: InterlockedIteratorAdvance
// FullName: InterlockedIteratorAdvance<RandomAccessIterator>
// Access: global
//************************************
template <class RandomAccessIterator>
void InterlockedIteratorAdvance( RandomAccessIterator & iterator, long const offset )
{
#if _SECURE_SCL || _HAS_ITERATOR_DEBUGGING
// Perform/force a manual check as the real modification will be done
// "behind the checking code's back".
RandomAccessIterator( iterator ) + offset;
InterlockedAdd( iterator._Myptr, offset );
#else
assert( sizeof( firstChannelBeginning_ ) == sizeof( long ) );
assert( std::tr1::has_trivial_destructor<RandomAccessIterator>::value );
InterlockedAdd( iterator , offset );
#endif
}
} // namespace anonymous
//************************************
// Method: MultiChannelBuffer
// FullName: MultiChannelBuffer::MultiChannelBuffer
// Access: public
//************************************
MultiChannelBuffer::MultiChannelBuffer()
:
buffer_ ( 1 ), // this actually reduces the binary by a chunk (512 bytes)...!?
channelSize_ ( 0 ),
bytesWritten_ ( 0 ),
minimumSpaceThatMustRemain_( 0 ),
writePosition_ ( buffer_.begin() )
{
}
//************************************
// Method: allReadersHaveTheSameEndMarker
// FullName: MultiChannelBuffer::allReadersHaveTheSameEndMarker
// Access: private
//************************************
bool MultiChannelBuffer::allReadersHaveTheSameEndMarker() const
{
// (todo) This check requires atomic iterator (which is non-pod structure
// under secure SCL) access.
#if 0 // Disabled until fully implemented.
Buffer::const_iterator const endMarker( writeCursor() );
for( Readers::const_iterator pReader( ++readers_.begin() ); pReader != readers_.end(); ++pReader )
{
Reader & reader( **pReader );
if ( ( reader.firstChannelBeginning_ + reader.availableDataPerChannel_ ) != endMarker )
return false;
}
#endif // Disabled until fully implemented.
return true;
}
//************************************
// Method: canWrite
// FullName: MultiChannelBuffer::canWrite
// Access: public
//************************************
size_t MultiChannelBuffer::canWrite() const
{
Buffer::const_iterator const readPosition( slowestReaderPosition() );
size_t const unreadSize ( writeCursor() - readPosition );
// The unread size could be read directly from the "slowest reader"
// ( unreadSize == slowestReader().availableDataPerChannel() ) but that
// would require reading it atomically/locked together with its position
// (firstChannelBeginning) because they are modified independently from
// different threads so this is simpler to do.
if ( unreadSize == 0 )
{
assert( allReadersHaveTheSameEndMarker() );
for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
{
// If even the slowest reader has nothing to read then all readers
// (must) have nothing to read.
assert( ( **pReader ).availableDataPerChannel_ == 0 );
assert( ( **pReader ).firstChannelBeginning_ == readPosition );
( **pReader ).firstChannelBeginning_ = buffer_.begin();
}
writePosition_ = buffer_.begin();
assert( allReadersHaveTheSameEndMarker() );
return channelSize();
}
// If we are at/near the end of the buffer and the
// minimumSpaceThatMustRemain_ threshold has been reached move the remaining
// data to the beginning of the buffer.
// (todo) This design and code is modeled against the WinAMP API and
// input-output module interaction with which a maximum size limit
// for write chunks is known (it is currently coupled so much that
// even the maxWAInputChunk/8192 deduced from the WA SDK out.h header
// is directly copied here instead of specified from outside). Once
// the 'final' design settles and the reusability potential of the
// MultiChannelBuffer class becomes more clear this coupling should
// be removed.
size_t const spaceAtEnd( channelSize() - writeCursorOffset() );
size_t const maxInputChunk( 8192 ); // maxWAInputChunk
if ( ( spaceAtEnd < maxInputChunk ) && ( unreadSize < minimumSpaceThatMustRemain_ ) )
{
assert( allReadersHaveTheSameEndMarker() );
size_t const spaceAtBeginning( readPosition - buffer_.begin() );
assert( ( spaceAtBeginning > spaceAtEnd ) && "Too small buffer size chosen. minimumSpaceThatMustRemain_ should be 'much smaller' than the total buffer size" );
assert( ( spaceAtBeginning >= unreadSize ) && "Buffer overlap. Probably a too small buffer. Requires memmove(). Thread unsafe." );
// (todo) try to optimize this so that the source and the destinatiion
// are always properly aligned to for the SSE optimized routine
// so that VEC_memcpy() can be called directly.
for( Buffer::iterator pChannelBuffer( buffer_.begin() ); pChannelBuffer < buffer_.end(); pChannelBuffer += channelSize() )
std::memcpy( &*pChannelBuffer, &pChannelBuffer[ spaceAtBeginning ], unreadSize );
for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
// "reader.firstChannelBeginning_ -= readCursorOffset;":
InterlockedIteratorAdvance( (**pReader).firstChannelBeginning_, -static_cast<long>( spaceAtBeginning ) );
writePosition_ = buffer_.begin() + unreadSize;
assert( spaceAtBeginning + spaceAtEnd == channelSize() - writeCursorOffset() );
assert( allReadersHaveTheSameEndMarker() );
return spaceAtBeginning + spaceAtEnd;
}
return spaceAtEnd;
}
//************************************
// Method: createNewReader
// FullName: MultiChannelBuffer::createNewReader
// Access: public
//************************************
MultiChannelBuffer::Reader MultiChannelBuffer::createNewReader() const
{
assert( isInResetState() );
return Reader( buffer_.begin(), *this );
}
//************************************
// Method: getWritePointers
// FullName: MultiChannelBuffer::getWritePointers
// Access: public
//************************************
MultiChannelBuffer::WritePointers MultiChannelBuffer::getWritePointers( size_t const sz )
{
assert( sz <= ( channelSize() - writeCursorOffset() ) ); // "Non adjusting" canWrite.
return WritePointers( writePosition_, channelSize(), sz, *this );
}
//************************************
// Method: hasUnreadInput
// FullName: MultiChannelBuffer::hasUnreadInput
// Access: public
//************************************
bool MultiChannelBuffer::hasUnreadInput() const
{
//return slowestReader().availableDataPerChannel() != 0;
for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
if ( (*pReader)->availableDataPerChannel() != 0 )
return true;
return false;
}
//************************************
// Method: isInResetState
// FullName: MultiChannelBuffer::isInResetState
// Access: private
//************************************
bool MultiChannelBuffer::isInResetState() const
{
for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
if ( (*pReader)->firstChannelBeginning_ != buffer_.begin() )
return false;
return !bytesWritten_ && ( writePosition_ == buffer_.begin() );
}
//************************************
// Method: isReaderRegistered
// FullName: MultiChannelBuffer::isReaderRegistered
// Access: private
//************************************
bool MultiChannelBuffer::isReaderRegistered( Reader const & reader ) const
{
return std::find( readers_.begin(), readers_.end(), &reader ) != readers_.end();
}
//************************************
// Method: registerReader
// FullName: MultiChannelBuffer::registerReader
// Access: private
//************************************
void MultiChannelBuffer::registerReader( Reader & reader ) const
{
assert( isInResetState() );
readers_.push_back( &reader );
}
//************************************
// Method: reset
// FullName: MultiChannelBuffer::reset
// Access: public
//************************************
void MultiChannelBuffer::reset()
{
bytesWritten_ = 0;
writePosition_ = buffer_.begin();
for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
{
Reader & reader( **pReader );
reader.firstChannelBeginning_ = buffer_.begin();
reader.availableDataPerChannel_ = 0;
}
assert( isInResetState() );
}
//************************************
// Method: resize
// FullName: MultiChannelBuffer::resize
// Access: public
//************************************
void MultiChannelBuffer::resize( size_t const numberOfChannels, size_t const bytesPerChannel, size_t const maximumExpectedWriteChunk )
{
buffer_.resize( numberOfChannels * bytesPerChannel );
channelSize_ = bytesPerChannel;
assert( numberOfChannels == this->numberOfChannels() );
minimumSpaceThatMustRemain_ = maximumExpectedWriteChunk;
reset();
}
//************************************
// Method: slowestReader
// FullName: MultiChannelBuffer::slowestReader
// Access: private
//************************************
Buffer::const_iterator MultiChannelBuffer::slowestReaderPosition() const
{
// (todo) recheck this for thread safety.
Buffer::const_iterator position( (*readers_.begin())->firstChannelBeginning_ );
for ( Readers::const_iterator pCurrentReader( ++readers_.begin() ); pCurrentReader != readers_.end(); ++pCurrentReader )
{
if ( (*pCurrentReader)->firstChannelBeginning_ < position )
position = (*pCurrentReader)->firstChannelBeginning_;
}
return position;
}
//************************************
// Method: unregisterReader
// FullName: MultiChannelBuffer::unregisterReader
// Access: private
//************************************
void MultiChannelBuffer::unregisterReader( Reader & reader ) const
{
assert( isReaderRegistered( reader ) );
readers_.erase( std::find( readers_.begin(), readers_.end(), &reader ) );
}
//************************************
// Method: writeCompletedCallback
// FullName: MultiChannelBuffer::writeCompletedCallback
// Access: public
//************************************
void MultiChannelBuffer::writeCompletedCallback( size_t const sizeOfWrittenChunk )
{
writePosition_ += sizeOfWrittenChunk;
bytesWritten_ += sizeOfWrittenChunk;
for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
InterlockedAdd( (**pReader).availableDataPerChannel_, sizeOfWrittenChunk );
}
//************************************
// Method: Reader
// FullName: MultiChannelBuffer::Reader::Reader
// Access: public
//************************************
MultiChannelBuffer::Reader::Reader( Reader const & source )
:
firstChannelBeginning_ ( source.firstChannelBeginning_ ),
availableDataPerChannel_( source.availableDataPerChannel_ ),
parent_ ( source.parent_ )
{
parent_.registerReader( *this );
}
//************************************
// Method: operator++
// FullName: MultiChannelBuffer::Reader::ChannelIterator::operator++
// Access: public
//************************************
MultiChannelBuffer::Reader::ChannelIterator & MultiChannelBuffer::Reader::ChannelIterator::operator++()
{
#if _SECURE_SCL || _HAS_ITERATOR_DEBUGGING
Buffer const & source( reinterpret_cast<Buffer const &>( *currentBegin_._Getmycont() ) );
if ( ( source.end() - this->end() ) < static_cast<Buffer::difference_type>( channelSize_ ) )
{
currentBegin_ = source.end();
availableDataPerChannel_ = 0;
}
else
#endif
currentBegin_ += channelSize_;
return *this;
}
//************************************
// Method: getChunk
// FullName: MultiChannelBuffer::Reader::getChunk
// Access: public
//************************************
MultiChannelBuffer::Reader::ChannelIterator MultiChannelBuffer::Reader::getChunk( size_t chunkSize )
{
assert( chunkSize < parent_.channelSize() );
chunkSize = (std::min)( chunkSize, availableDataPerChannel() );
ChannelIterator const result( firstChannelBeginning_, chunkSize, parent_.channelSize() );
// "availableDataPerChannel_ -= chunkSize;":
InterlockedAdd( availableDataPerChannel_, -static_cast<int>( chunkSize ) );
// "firstChannelBeginning_ += chunkSize;":
InterlockedIteratorAdvance( firstChannelBeginning_, chunkSize );
return result;
}
//************************************
// Method: WritePointers
// FullName: MultiChannelBuffer::WritePointers::WritePointers
// Access: public
//************************************
MultiChannelBuffer::WritePointers::WritePointers( Buffer::iterator const firstChannelWritePosition, size_t const channelSize, size_t const writeChunkSize, MultiChannelBuffer & parent )
:
firstChannelWritePosition_( firstChannelWritePosition ),
channelSize_ ( channelSize ),
writeChunkSize_ ( writeChunkSize ),
parent_ ( parent )
{
}
#ifdef _DEBUG // Ugly workaround for the RVO not working in DEBUG builds.
//************************************
// Method: WritePointers
// FullName: MultiChannelBuffer::WritePointers::WritePointers
// Access: public
//************************************
MultiChannelBuffer::WritePointers::WritePointers( MultiChannelBuffer::WritePointers const & source )
:
firstChannelWritePosition_( source.firstChannelWritePosition_ ),
channelSize_ ( source.channelSize_ ),
writeChunkSize_ ( source.writeChunkSize_ ),
parent_ ( source.parent_ )
{
const_cast<size_t &>( source.writeChunkSize_ ) = 0;
}
#endif
//************************************
// Method: ~WritePointers
// FullName: MultiChannelBuffer::WritePointers::~WritePointers
// Access: public
//************************************
MultiChannelBuffer::WritePointers::~WritePointers()
{
#ifdef _DEBUG // Ugly workaround for the RVO not working in DEBUG builds.
if ( writeChunkSize_ )
#endif
parent_.writeCompletedCallback( writeChunkSize_ );
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.